Bilangan complex pada chapter sebelumnya adalah vektor dua komponen yang matematikanya sudah siap pakai; sekarang kita generalisasi ke ukuran berapa pun — vektor n komponen dan matriks baris × kolom — dan membahas pertanyaan yang sering dijawab tergesa-gesa: bagaimana menyimpannya di C++. Ada tiga kandidat utama, dan pilihan Anda menentukan bukan sekadar gaya kode, melainkan kecepatan juga: std::array untuk dimensi kecil yang tetap, std::vector<std::vector<double>> yang nyaman tetapi menyebar di memori, dan flat vector — satu std::vector panjang dengan indeks dua dimensi yang dihitung manual lewat rumus i*cols + j. Chapter ini membangun ketiganya, menghitung dot product dan perkalian matriks pada representasi flat, lalu menjelaskan mengapa pustaka numerik sungguhan (BLAS, LAPACK, dan kawan-kawannya) hampir selalu memilih larik datar.

Tiga Kandidat Representasi #

RepresentasiCocok untukKelebihanKelemahan
std::array<double, N>dimensi kecil yang tetap (3D, kuaternion)tanpa alokasi dinamis, ukuran diketahui saat kompilasi, se-kontiguukuran terkunci di tipe
vector<vector<double>>prototyping, baris yang panjangnya berbeda (jagged)sintaks m[i][j] familiar, tiap baris bisa dibuang/gantibanyak alokasi, data tercecer, baris tidak harus konsisten
flat vector<double> + indekskode numerik serius, data besarsatu blok memori kontigu, satu alokasi, siap dikirim ke pustaka C/Fortranindeks manual i*cols + j harus disiplin

Konvensi penyimpanan flat di atas adalah row-major: elemen baris 0 diletakkan lebih dulu, lalu baris 1, dan seterusnya — persis cara C dan C++ menyimpan array multi-dimensi bawaan. Indeks (i, j) menerjemahkan ke posisi i * cols + j, dan fungsi kecil bernama at akan membungkus rumus itu supaya sisa kode tetap terbaca seperti matematika.

Dot Product, Indeks Flat, dan Perkalian Matriks #

cpp
#include <array>
#include <iostream>
#include <vector>

int main() {
    // 1. Vektor kecil berukuran tetap: std::array
    std::array<double, 3> a{1.0, 2.0, 3.0};
    std::array<double, 3> b{4.0, 5.0, 6.0};
    double dot = 0.0;
    for (std::size_t i = 0; i < a.size(); ++i) {
        dot += a[i] * b[i];
    }
    std::cout << "dot(a,b)  = " << dot << "\n";

    // 2. Matriks nested: setiap baris adalah vector tersendiri
    std::vector<std::vector<double>> nested = {
        {1.0, 2.0, 3.0},
        {4.0, 5.0, 6.0},
    };
    std::cout << "nested    = " << nested[1][0] << " " << nested[1][1]
              << " " << nested[1][2] << "\n";

    // 3. Matriks flat: satu vector, indeks dua dimensi dihitung manual
    const int rows = 2;
    const int cols = 3;
    std::vector<double> flat = {
        1.0, 2.0, 3.0,   // baris 0
        4.0, 5.0, 6.0,   // baris 1
    };
    auto at = [&](int i, int j) { return flat[i * cols + j]; };
    std::cout << "flat(1,0) = " << at(1, 0) << ", flat(0,2) = "
              << at(0, 2) << "\n";

    // 4. Perkalian matriks 2x3 * 3x2 = 2x2 pada representasi flat
    std::vector<double> bMatriks = {
        7.0,   8.0,
        9.0,  10.0,
        11.0, 12.0,
    };
    std::vector<double> c(4, 0.0);
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < 2; ++j) {
            for (int l = 0; l < cols; ++l) {
                c[i * 2 + j] += at(i, l) * bMatriks[l * 2 + j];
            }
        }
    }
    std::cout << "A x B     = " << c[0] << " " << c[1] << "\n";
    std::cout << "            " << c[2] << " " << c[3] << "\n";

    return 0;
}

Output yang Dihasilkan:

text
dot(a,b)  = 32
nested    = 4 5 6
flat(1,0) = 4, flat(0,2) = 3
A x B     = 58 64
            139 154

Bedah baris-per-baris:

  1. Baris 7-13 (std::array<double, 3> a ... dot += a[i] * b[i];): dot product menjumlahkan hasil kali komponen yang seposisi: 1×4 + 2×5 + 3×6 = 32. std::array menampung enam double ini di stack tanpa satu pun alokasi heap — pilihan alami untuk vektor 3D grafika dan fisika.
  2. Baris 16-19 (std::vector<std::vector<double>> nested = {...}): matriks nested sesungguhnya adalah vector berisi dua vector baris. Akses nested[1][0] berarti dua lompatan: ambil baris ke-1 (sebuah objek vector), lalu elemen ke-0 di dalamnya.
  3. Baris 24-30 (const int cols = 3; ... auto at = [&](int i, int j) ...): data yang sama disimpan sebagai enam double berurutan, dan lambda at menerapkan rumus row-major i * cols + j. Uji kecilnya konsisten: flat(1,0) = posisi 3 bernilai 4, flat(0,2) = posisi 2 bernilai 3 — sama dengan matriks aslinya.
  4. Baris 35-39 (std::vector<double> bMatriks = {...}): matriks B berukuran 3×2 juga flat — tiga baris, dua kolom, diakses dengan l * 2 + j (dua adalah jumlah kolom B).
  5. Baris 40-47 (std::vector<double> c(4, 0.0); ... tiga loop ...): inilah algoritma perkalian matriks klasik: setiap sel C[i][j] menjumlahkan hasil kali baris i dari A dengan kolom j dari B. Tiga loop luar-dalam-luar menghasilkan 2×2 sel, masing-masing tiga kali kali-tambah.
  6. Baris 48-49 (std::cout << "A x B = " ...): hasil mengikuti hitungan manual — 1×7+2×9+3×11 = 58, 1×8+2×10+3×12 = 64, 4×7+5×9+6×11 = 139, 4×8+5×10+6×12 = 154. Pencetakan dua baris menjaga bentuk matriksnya tetap terlihat.

Flat vs Nested: Cerita Cache Locality #

Perbedaan dua representasi itu bukan soal selera, melainkan soal cache locality. CPU modern mengambil data dari memori utama dalam blok-blok berurutan (cache line); ketika enam double matriks flat terletak berdempetan, pembacaan berurutan mendapat blok-blok itu “gratis” dari prefetcher hardware. Matriks nested memaksa tiap baris mengalokasi sendiri di lokasi yang bisa berjauhan — akses [i][j] adalah pointer chasing: dereferensi baris dulu, baru elemen. Ditambah lagi, nested butuh rows + 1 alokasi dinamis, sementara flat cukup satu. Untuk matriks ribuan × ribuan, perbedaan ini bisa berkali lipat kecepatannya, dan itulah alasan pustaka numerik dunia (BLAS/LAPACK) menyimpan semuanya sebagai satu larik datar dengan stride. Kita hanya menyentuh dasarnya di sini; pengukuran dan teknik mempercepat urutan loop dibahas tuntas di Section Performa dan Optimasi.

Setelah data terstruktur rapi, lengkaplah sudah bekalnya: sekarang kita tinggal memanggil fungsi matematika standar. Chapter Operasi Numerik berikutnya menelusuri <cmath> — dari sqrt sampai remainder — beserta cara aman menangani keluaran tak terdefinisi dan konversi antar tipe angka.

Rujukan resmi untuk topik Chapter ini: std::array , std::vector , dan std::mdspan di cppreference.