Array

Bekerja dengan array [N]T berpanjang tetap yang ukurannya diketahui saat kompilasi.

Daftar isi

Array [N]T menyimpan sejumlah elemen bertipe T yang panjangnya tetap dan diketahui saat kompilasi. Panjang tersebut menjadi bagian dari tipe, sehingga kekeliruan jumlah elemen tertangkap sebelum program berjalan. Topik ini membahas deklarasi dan inisialisasi array, sifat panjangnya sebagai konstanta waktu kompilasi, iterasi dengan for, array dua dimensi, serta perilaku penyalinan ketika array ditugaskan. Program statistik nilai ujian di bagian tengah memperagakan iterasi dan perhitungan atas data nyata yang dijalankan dengan Zig 0.17.0.

Konsep#

Mendeklarasikan dan menginisialisasi array#

Tipe array menuliskan panjangnya di dalam kurung siku sebelum tipe elemen, sehingga [8]u32 adalah array berisi delapan u32. Panjang itu merupakan bagian dari tipe, dan dua array dengan panjang berbeda adalah dua tipe yang berbeda. Inisialisasi menyatakan seluruh elemen secara eksplisit, dan Zig menolak inisialisasi yang jumlah elemennya kurang.

zig Contoh: dua bentuk inisialisasi array
// tidak-dijalankan
const a = [3]u32{ 10, 20, 30 };
const b: [3]u32 = .{ 10, 20, 30 }; // bentuk ringkas, tipe dari anotasi

Bentuk pertama menuliskan tipe array secara lengkap, sedangkan bentuk kedua menyerahkan tipe kepada anotasi dan memakai sintaks titik yang ringkas. Kompilator memeriksa jumlah elemen terhadap panjang yang dinyatakan, dan pesan errornya, misalnya expected 3 array elements; found 2, menunjuk langsung pada jumlah yang kurang. Upaya menugaskan [5]u32 pada [4]u32 juga ditolak dengan pesan expected type '[4]u32', found '[5]u32' karena keduanya memang tipe yang berbeda. Aturan ini membuat ukuran data tetap selalu konsisten dari deklarasi sampai pemakaiannya.

Kedua bentuk inisialisasi menghasilkan array yang identik, sehingga pilihannya adalah soal keterbacaan. Bentuk lengkap lebih informatif pada deklarasi yang berdiri sendiri, sedangkan bentuk ringkas lebih nyaman ketika tipenya panjang dan tulisannya menjadi repetitif. Apapun bentuknya, jumlah elemen yang ditulis wajib sama persis dengan panjang yang dinyatakan, dan tidak ada inisialisasi sebagian.

Panjang sebagai konstanta waktu kompilasi#

Properti len menyatakan panjang array dan nilainya diketahui pada waktu kompilasi. Nilai itu dapat dipakai di mana pun konstanta waktu kompilasi diharapkan, termasuk sebagai panjang array lain.

zig Contoh: panjang array sebagai dimensi array lain
// tidak-dijalankan
const nilai = [8]u32{ 72, 88, 95, 64, 79, 91, 85, 70 };
const salinan: [nilai.len]u32 = nilai;

Deklarasi salinan pada contoh memakai nilai.len sebagai panjangnya, sesuatu yang hanya mungkin karena panjang array memang bagian dari tipe. Karena len bersifat konstanta, membacanya tidak menimbulkan biaya apa pun pada runtime. Kaidah praktisnya, perulangan dan pembagian untuk menghitung rata-rata memakai nilai.len tanpa menghitung ulang jumlah elemen secara manual. Pendekatan tersebut menghilangkan satu sumber kekeliruan klasik, yaitu panjang yang tertinggal ketika data diperluas.

Sifat ini berbeda dengan bahasa yang menyimpan panjang array pada variabel runtime, karena Zig menuliskan panjang langsung pada tipenya. Akibat positifnya, array tidak memerlukan ruang tambahan untuk menyimpan panjangnya sendiri. Akibat negatifnya, array dengan panjang berbeda tidak dapat dipertukarkan begitu saja, dan kebutuhan koleksi yang panjangnya baru diketahui saat berjalan dilayani oleh slice yang membawa panjangnya pada runtime.

Iterasi dengan for#

Perulangan for mengunjungi setiap elemen array dan mengikat nilainya pada satu pengenal, misalnya |n|. Bentuk kedua mengikat pasangan nilai dan indeks dengan penanda rentang 0.., sebagaimana |n, i|. Program berikut menghitung statistik nilai ujian dari array delapan elemen.

zig nilai-ujian.zig
const std = @import("std");

pub fn main() void {
    const nilai = [8]u32{ 72, 88, 95, 64, 79, 91, 85, 70 };

    var total: u32 = 0;
    var tertinggi: u32 = 0;

    for (nilai) |n| {
        total += n;
        if (n > tertinggi) {
            tertinggi = n;
        }
    }

    std.debug.print("Jumlah nilai: {d}\n", .{total});
    std.debug.print("Nilai tertinggi: {d}\n", .{tertinggi});
    std.debug.print("Rata-rata: {d}\n", .{total / nilai.len});

    for (nilai, 0..) |n, i| {
        if (n == tertinggi) {
            std.debug.print("Tertinggi pada indeks: {d}\n", .{i});
        }
    }
}
text Keluaran
Jumlah nilai: 644
Nilai tertinggi: 95
Rata-rata: 80
Tertinggi pada indeks: 2

Jalankan program tersebut dengan perintah berikut.

bash
# menjalankan program statistik nilai
zig run nilai-ujian.zig
text Keluaran
Jumlah nilai: 644
Nilai tertinggi: 95
Rata-rata: 80
Tertinggi pada indeks: 2

Perulangan pertama menjumlahkan seluruh nilai dan menyimpan nilai tertingginya, sedangkan pembagian total / nilai.len menghasilkan rata-rata bulat 80 karena kedua operannya bertipe integer. Perulangan kedua memakai bentuk |n, i| untuk menemukan posisi nilai tertinggi, yang ternyata berada pada indeks 2. Apabila rata-rata pecahan dibutuhkan, salah satu operan dikonversi ke tipe pecahan terlebih dahulu dengan fungsi konversi bawaan sebelum pembagian dilakukan.

Perulangan pada Zig tidak memiliki bentuk C klasik dengan penghitung yang dikelola manual, sehingga setiap perulangan menempuh seluruh elemen atau rentang yang dinyatakan secara eksplisit. Bentuk |n| dipakai ketika indeks tidak dibutuhkan, dan bentuk |n, i| dipakai ketika posisi elemen turut dihitung. Rentang 0.. pada contoh berarti seluruh indeks, sedangkan rentang seperti 0..4 dapat dipakai untuk membatasi kunjungan pada sebagian elemen saja.

Contoh Lengkap#

Array dua dimensi#

Array yang elemennya juga array membentuk struktur dua dimensi, ditulis [3][3]u8 untuk tiga baris yang masing-masing berisi tiga u8. Pengaksesannya memakai dua pasang kurung siku, misalnya papan[1][2] untuk baris 1 kolom 2. Inisialisasi bersarangnya memakai sintaks titik pada setiap baris, dan setiap baris wajib memuat jumlah elemen yang sama.

zig Contoh: array dua dimensi
// tidak-dijalankan
const papan = [3][3]u8{
    .{ 1, 0, 1 },
    .{ 0, 1, 0 },
    .{ 1, 0, 1 },
};

Struktur ini cocok untuk data berbentuk tabel yang ukurannya tetap, misalnya papan permainan sederhana atau matriks kecil. Elemen-elemennya tetap disimpan berurutan baris demi baris dalam memori, sehingga ukurannya dapat dihitung sebagai panjang kali lebar. Untuk ukuran yang tidak diketahui saat kompilasi, array tidak lagi cocok dan koleksi yang tumbuh secara dinamis menjadi pilihan yang tepat. Pemilihan ini sebaiknya dilakukan sejak perancangan data agar perubahan ukuran tidak memaksa penulisan ulang tipe.

Penulisan [3][3]u8 dibaca dari luar ke dalam, yaitu array tiga baris yang masing-masing bertipe [3]u8. Nilai papan[1][1] pada contoh adalah 1, yaitu elemen tengah papan. Deklarasi yang lebih dalam seperti [2][3][3]u8 tetap sah untuk data tiga dimensi, dan pola pengaksesannya menambah satu pasang kurung siku per dimensi.

Array disalin saat ditugaskan#

Array bersifat nilai, artinya penugasan menyalin seluruh elemen dan bukan sekadar alamatnya. Variabel penerima memperoleh salinan yang berdiri sendiri, dan perubahan pada salinan tidak pernah menyentuh array asalnya.

zig salin.zig
const std = @import("std");

pub fn main() void {
    const a = [3]u32{ 1, 2, 3 };
    var b = a;
    b[0] = 99;
    std.debug.print("a[0]: {d}\n", .{a[0]});
    std.debug.print("b[0]: {d}\n", .{b[0]});
}
text Keluaran
a[0]: 1
b[0]: 99

Nilai a[0] tetap 1 meskipun b[0] telah diubah menjadi 99, karena a dan b menyimpan data masing-masing. Perilaku ini memastikan fungsi yang menerima array tidak dapat mengubah array milik pemanggilnya secara diam-diam. Untuk data yang besar, salinan penuh memakan memori dan waktu, dan itulah alasan Zig menyediakan slice sebagai tampilan atas data yang sama tanpa menyalinnya.

Penyalinan yang utuh berlaku pula ketika array menjadi nilai balik fungsi atau elemen dari struktur data lain. Untuk array kecil, biayanya tidak terasa, sedangkan untuk array berisi ratusan elemen, penyalinan yang berulang menjadi beban yang terukur. Ketika data harus dibagikan tanpa salinan, Zig memindahkan persoalannya ke tipe yang menyimpan alamat memori, dan slice adalah bentuknya yang paling umum di dalam pustaka standar.

Kesalahan umum#

Kesalahan pertama berupa menulis panjang array sebagai var dengan harapan array akan tumbuh. Panjang array adalah konstanta waktu kompilasi, dan percobaan mengubahnya ditolak pada waktu kompilasi. Struktur yang tumbuh dinamis adalah pekerjaan ArrayList pada modul Memori dan allocator, sedangkan array tetap berukuran tetap selamanya.

Kesalahan kedua berupa menganggap penugasan array menyalin referensi. Array penuh disalin nilainya saat ditugaskan, sehingga perubahan pada salinan tidak terbaca pada aslinya. Perilaku ini berbeda dari slice pada topik berikutnya, dan kerancuan antara keduanya menjadi sumber kebingungan yang paling sering.

Ringkasan#

  • Tipe array memuat panjangnya, misalnya [8]u32, sehingga array dengan panjang berbeda adalah tipe berbeda dan inisialisasi yang kurang elemen ditolak saat kompilasi.
  • Properti len adalah konstanta waktu kompilasi yang dapat dipakai sebagai dimensi array lain maupun sebagai pembagi perhitungan.
  • Perulangan for mengikat elemen dengan |n| atau pasangan elemen dan indeks dengan |n, i|.
  • Penugasan array menyalin seluruh elemen, sehingga setiap variabel menyimpan data yang berdiri sendiri dan perubahan salinan tidak menyentuh asalnya.

Referensi#