Setiap algoritma yang kita tulis pada akhirnya beroperasi pada data: angka, karakter, kebenaran suatu kondisi. C++ adalah bahasa yang statically typed, artinya compiler harus mengetahui tipe setiap variabel pada saat kompilasi. Pemilihan tipe yang tepat menentukan seberapa cepat dan seberapa aman kode kita berjalan. Pada Chapter ini kita akan mempelajari tipe fundamental C++ yang paling sering dipakai di DSA, cara menginisialisasi variabel, serta cara compiler melakukan konversi antar tipe.
Tipe Fundamental untuk DSA #
C++ menyediakan banyak tipe bawaan, namun hanya sebagian kecil yang sering kita pakai saat menulis struktur data dan algoritma. Pemilihan tipe menentukan rentang nilai yang bisa ditampung sekaligus konsumsi memori. Tabel berikut merangkum tipe yang paling relevan:
| Tipe | Ukuran tipikal | Rentang | Penggunaan umum di DSA |
|---|---|---|---|
int | 4 byte | ±2,1 miliar | Counter, indeks kecil |
long long | 8 byte | ±9,2 × 10¹⁸ | Hasil perhitungan besar, kombinasi |
std::size_t | 8 byte | 0 .. 1,8 × 10¹⁹ | Ukuran container, indeks array |
char | 1 byte | karakter ASCII | Pemrosesan string |
bool | 1 byte | true / false | Flag, kondisi |
double | 8 byte | ~15 digit desimal | Geometri, statistik |
Mekanisme: Variabel adalah Slot Memori #
Ketika kita mendeklarasikan int jumlah_node = 1'000;, compiler memesan slot memori sebesar sizeof(int) byte, menempelkan nama jumlah_node ke alamat slot tersebut, dan menulis nilai 1000 ke dalamnya. Nama variabel hanyalah label yang dipakai compiler untuk menerjemahkan kode menjadi akses memori.
Kode: Deklarasi dan Inisialisasi #
#include <iostream>
#include <limits>
int main() {
int jumlah_node = 1'000;
long long kombinasi = 9'000'000'000'000LL;
std::size_t n = 42;
char inisial = 'G';
bool selesai = false;
std::cout << "jumlah_node = " << jumlah_node << '\n';
std::cout << "kombinasi = " << kombinasi << '\n';
std::cout << "n = " << n << '\n';
std::cout << "inisial = " << inisial << '\n';
std::cout << "selesai = " << std::boolalpha << selesai << '\n';
std::cout << "int max = " << std::numeric_limits<int>::max() << '\n';
std::cout << "long long max= " << std::numeric_limits<long long>::max() << '\n';
return 0;
}Output yang Dihasilkan:
jumlah_node = 1000
kombinasi = 9000000000000
n = 42
inisial = G
selesai = false
int max = 2147483647
long long max= 9223372036854775807Mari kita telaah baris-baris kode di atas:
- Baris
int jumlah_node = 1'000;: compiler memesan 4 byte, menulis1000. Tanda'adalah digit separator yang hanya memperjelas angka besar; nilainya tetap1000. - Baris
long long kombinasi = 9'000'000'000'000LL;: sufiksLLmemaksa literal menjadilong longkarena angka sebesar itu melebihiint max. - Baris
std::size_t n = 42;:size_tadalah alias unsigned 64-bit. Kita pakai untuk panjang container dan indeks array. - Baris
std::boolalpha: manipulator yang membuatbooldicetak sebagaitrue/falsealih-alih1/0. - Baris
std::numeric_limits<int>::max(): cara portabel memperoleh batas atas tipe.
Inisialisasi: Brace vs Equals #
C++ modern menyediakan tiga gaya inisialisasi yang sering membingungkan pemula. Pilihannya bukan sekadar selera gaya — masing-masing memiliki makna teknis berbeda.
| Gaya | Sintaks | Karakteristik |
|---|---|---|
| Copy initialization | int a = 10; | Warisan dari C; mengizinkan narrowing implisit |
| Direct list init | int b{20}; | Modern; menolak narrowing |
| Value initialization | int c{}; | Mengisi nol untuk tipe fundamental |
Kode: Tiga Gaya Inisialisasi #
#include <iostream>
int main() {
int a = 10; // copy initialization
int b{20}; // direct list initialization
int c{}; // value initialization -> 0
std::cout << "a,b,c = " << a << ',' << b << ',' << c << '\n';
// int bad{3.14}; // ERROR: narrowing conversion dari double ke int
double pi = 3.14;
int bulat = pi; // OK secara sintaks, tapi memotong 0.14
std::cout << "bulat = " << bulat << '\n';
return 0;
}Output yang Dihasilkan:
a,b,c = 10,20,0
bulat = 3- Baris
int b{20};: brace{}memaksa pemeriksaan narrowing. Compiler akan menolakint bad{3.14}karena 3.14 tidak muat sebagaiinttanpa kehilangan presisi. - Baris
int c{};: tanda kurung kurawal kosong melakukan value initialization. Untuk tipe fundamental, hasilnya0. Untukstd::string, hasilnya string kosong. - Baris
int bulat = pi;: copy initialization mengizinkan konversi implisitdouble → intyang memotong bagian pecahan. Operasi ini sah, tetapi kehilangan presisi sering menjadi sumber bug DSA yang sulit dilacak.
auto dan decltype #
Menulis tipe yang panjang seperti std::vector<int>::iterator berulang-ulang melelahkan. C++ menyediakan auto agar compiler menyimpulkan tipe dari ekspresi inisialisasi, dan decltype untuk memperoleh tipe dari ekspresi yang sudah ada.
Kode: Penyimpulan Tipe Otomatis #
#include <iostream>
#include <vector>
int main() {
std::vector<int> data{10, 20, 30};
auto ukuran = data.size(); // std::size_t
auto total = data[0] + data[1]; // int
decltype(total) cadangan = 0; // int (mengikuti tipe total)
std::cout << "ukuran = " << ukuran << '\n';
std::cout << "total = " << total << '\n';
std::cout << "cadangan = " << cadangan << '\n';
return 0;
}Output yang Dihasilkan:
ukuran = 3
total = 30
cadangan = 0- Baris
auto ukuran = data.size();:size()mengembalikanstd::size_t, jadiukuranjuga bertipestd::size_t. Menggunakanautomenghindari kesalahan tipe yang sering muncul bila kita menulisint. - Baris
auto total = data[0] + data[1];: penjumlahan duaintmenghasilkanint, sehinggatotalbertipeint. - Baris
decltype(total) cadangan = 0;:decltypemengambil tipe variabeltotal(yaituint) tanpa mengevaluasi nilainya, lalu mendeklarasikancadangandengan tipe yang sama.
Konversi Tipe: Eksplisit vs Implisit #
Konversi tipe terjadi ketika dua tipe berbeda berinteraksi dalam satu ekspresi. C++ melakukan implicit conversion untuk tipe yang kompatibel, namun untuk konversi yang berisiko kita perlu menyatakannya secara eksplisit.
Mekanisme: Promotion dan Narrowing #
Saat kita menulis int + double, compiler mempromosikan int menjadi double agar operasi berlangsung tanpa kehilangan presisi. Sebaliknya, double → int adalah narrowing yang membuang bagian pecahan. Untuk konversi yang sengaja dilakukan, gunakan static_cast<Tipe>(nilai).
Kode: Rasio dan Overflow #
#include <iostream>
#include <limits>
int main() {
int jumlah_node = 1000;
double rasio = static_cast<double>(jumlah_node) / 7;
std::cout << "rasio = " << rasio << '\n';
int big = std::numeric_limits<int>::max();
std::cout << "big = " << big << '\n';
std::cout << "big + 1 = " << big + 1 << '\n'; // overflow
long long safe = static_cast<long long>(big) + 1;
std::cout << "safe = " << safe << '\n';
return 0;
}Output yang Dihasilkan:
rasio = 142.857
big = 2147483647
big + 1 = -2147483648
safe = 2147483648- Baris
static_cast<double>(jumlah_node) / 7: kita memaksajumlah_nodemenjadidoublelebih dulu, sehingga pembagian dilakukan dalam presisi pecahan. Tanpa cast,1000 / 7menghasilkan142(integer division). - Baris
big + 1:bigberisiINT_MAX. Menambahkan1menyebabkan signed integer overflow, yang dalam C++ adalah undefined behavior. Pada platform umum hasilnya membungkus ke nilai negatif terbesar. - Baris
static_cast<long long>(big) + 1: cast kelong longlebih dulu memperluas rentang, sehingga penambahan menjadi aman.
Ringkasan #
- Pilih tipe sesuai kebutuhan:
intuntuk counter kecil,long longuntuk hasil besar,std::size_tuntuk ukuran/panjang. - Gunakan brace initialization
int x{value};agar compiler menjaga dari narrowing. - Manfaatkan
autodandecltypeuntuk kode yang ringkas tanpa kehilangan ketepatan tipe. - Gunakan
static_cast<Tipe>(nilai)untuk konversi yang sengaja dilakukan, dan jangan abaikan kemungkinan overflow.