Skip to main content

Array and Basic Pointer

Pada C, ada hubungan kuat antara Array dan Pointer, sehingga Pointer dan Array harus dibahas secara bersamaan.

Memory and Addresses​

Mari kita mulai dengan gambaran sederhana bagaimana data sementara disimpan di dalam komputer. Pada umumnya data sementara, seperti nilai atau deklarasi variabel pada komputer disimpan di dalam sel memori yang diberi nomor atau alamat secara berurutan yang biasanya terdapat pada perangkat keras Main memory (RAM).

Sel-sel tersebut dapat direpresentasikan seperti kotak-kotak kecil berikut:

Kita misalkan satu kotak tersebut merepresentasikan 1 byte di dalam memori.

Setiap byte memory memiliki alamat untuk memenentukan lokasi dari memori tersebut, yang lokasi tersebut ditulis dengan notasi hexadesimal (base-16) 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. Agar tidak menimbulkan ambiguitas ketika memiliki dua digit atau lebih hexadesimal seperti 1 0 yang angka desimalnya seharusnya 11, namun jika kita tidak mengetahui bahwa angka tersebut ditulis dalam notasi hexadesimal kita akan mengiranya sebagai angka desimal 10. Untuk mengatasi hal tersebut kita menambahkan prefix 0x didepan sebagai penanda bahwa angka tersebut dalam notasi hexadesimal.

0x000000000x000000010x000000020x000000030x00000004· · · · · ·0xffffffff

Ketika kita menginisialisasi sebuah variabel misalnya

int N = 26;

variable tersebut akan disimpan di dalam memori, di lokasi random dengan ukuran 4 Byte.

26N

Seperti yang kita ketahui pada pertemuan pertama, Variable adalah nama lokasi penyimpanan di memori.

Ketika kita memanggil variable N, misalnya pada

printf("%d", N);

Komputer akan menuju lokasi tersebut didalam memori kemudian mengembalikan nilainya yaitu 26 dan mencetaknya menggunakan function printf.

Seperti yang kita ingat, kita pernah menggunakan simbol & (Ampersand) pada scanf. Simbol & untuk mengatakan "at the address of".

Sebagai contoh:

scanf("%d", &N);

Sebenarnya simbol & mengembalikan alamat dari sebuah variable, pada kasus tersebut mengembalikan alamat dari lokasi variable N.

Kita pun dapat melihat alamat yang dikembalikannya dengan mencetaknya menggunakan printf:

printf("%p", &N);

Outputnya adalah berupa alamat dari variable N dalam format notasi hexadesimal.

Pointers​

Singkatnya Pointer adalah variable yang menyimpan alamat dari variable lain.

Jika variabel biasa menyimpan nilai secara langsung, pointer menyimpan lokasi dari nilai tersebut di memori. Ia bekerja seperti "penunjuk" yang menunjukkan di mana sebuah data berada.

Referencing​

Untuk membuat sebuah pointer, Kita tinggal membuat suatu variable yang memiliki tipe data yang sama dengan variable yang alamatnya ingin kita simpan, dan menambahkan simbol * Asterisk di depan nama variablenya:

int *p;

Pernyataan di atas berarti p adalah sebuah pointer yang menunjuk ke tipe int. Tanda * di sini bukan operator, melainkan bagian dari deklarasi Referencing yang menandakan bahwa p adalah pointer, bukan variabel biasa.

Untuk menyimpan alamat dari sebuah variabel ke dalam pointer, kita gunakan operator & yang mengembalikan alamat dari variable:

p = &N;
0x0000012326pN

Sekarang p menyimpan alamat dari N. Jika kita cetak:

printf("%p", p);

Outputnya akan sama dengan printf("%p", &N);

Dereferencing​

Untuk mengakses nilai yang ditunjuk oleh pointer, kita gunakan operator * (disebut dereference):

printf("%d", *p);

Outputnya adalah 26, karena *p berarti "nilai yang berada di alamat yang disimpan oleh p".

Kita pun dapat mengubah nilai di variable N, dengan dereference *p

*p = 67; // Nilai N sekarang adalah 67
tip

Perhatikan bahwa * memiliki dua makna berbeda tergantung konteks:

  • Jika pada deklarasi int *p; → Referencing
  • Jika pada expression *p → Dereferencing

Ukuran Pointer​

Semua pointer memiliki ukuran yang sama terlepas dari tipe data yang ditunjuk. Ukuran pointer ditentukan oleh arsitektur sistem, bukan oleh tipe data yang ditunjuk:

ArsitekturUkuran PointerPenjelasan
32-bit4 byteAlamat memori direpresentasikan dalam 32 bit
64-bit8 byteAlamat memori direpresentasikan dalam 64 bit

Karena pointer menyimpan alamat memori, ukurannya sama dengan ukuran alamat pada arsitektur tersebut. Tipe data yang ditunjuk (int, char, float, dll) tidak memengaruhi ukuran pointer itu sendiri.

Kita dapat memverifikasi hal ini dengan operator sizeof:

printf("sizeof(int*) = %zu\n", sizeof(int*));
printf("sizeof(char*) = %zu\n", sizeof(char*));
printf("sizeof(float*)= %zu\n", sizeof(float*));
printf("sizeof(void*) = %zu\n", sizeof(void*));

Output pada sistem 64-bit:

sizeof(int*) = 8
sizeof(char*) = 8
sizeof(float*)= 8
sizeof(void*) = 8

Semua menghasilkan 8 byte, karena pada sistem 64-bit semua alamat memori berukuran 8 byte.


Array​

Array adalah kumpulan elemen yang memiliki tipe data sama dan disimpan secara berurutan di memori.

Why Need Array?​

Mari kita misalkan kita ingin menyimpan nilai ujian 5 mahasiswa:

int n1 = 70, n2 = 85, n3 = 90, n4 = 75, n5 = 88;

Cara tersebut masih bisa untuk 5 data. Bagaimana jika datanya 100? 1000? Kita akan kewalahan membuat variabel satu per satu. Untuk mengatasi hal tersebut kita gunakan Array, cukup satu nama untuk semua nilai:

int nilai[5] = {70, 85, 90, 75, 88};

Declaration of an Array​

dataType identifier[arraySize];

Sebagai contoh:

int a[10];

Membuat sebuah array berukuran 10 yang menyimpan integer.

a:a[0]a[1]a[9].   .   .

Kita juga dapat menginisialisasi langsung elemen dari array-nya dengan menggunakan {}.

Sebagai contoh:

int nilai[5] = {70, 85, 90, 75, 88};

Statement tersebut langsung membuat array bernama nilai berukuran 5 dengan nilai awal yang kita masukkan di dalam {}.

nilai:7085907588nilai[0]nilai[1]nilai[2]nilai[3]nilai[4]

Kita juga bisa membuat array seperti ini:

int suhu[] = {30, 25, 24, 35, 38, 10};
suhu:302524353810suhu[0]suhu[1]suhu[2]suhu[3]suhu[4]suhu[5]

Yang otomatis membuat array dengan ukuran sesuai dengan banyaknya elemen yang kita masukan pada inisialisasi.

note

Ketika array hanya dideklarasikan dan tidak diinisialisasi, ukuran array tidak boleh kosong.

Indexing​

Untuk menentukan posisi dari elemen, array menggunakan index, yaitu angka urutan posisi elemen di dalam array. Di C, Index dimulai dari 0.

Notasi namaArray[i] mengacu pada elemen ke-i di array.

Sebagai contoh pada array suhu sebelumnya, misalnya kita ingin mengakses elemen ke dua di dalam array tersebut.

// suhu[1] -> Mengembalikan nilai suhu pada elemen indeks ke 1
printf("%d", suhu[1]); // Output: 25

Kita juga dapat mengubah nilai elemen menggunakan indexing:

suhu[1] = 39; // Array Suhu = [30 39 24 35 38 10]

Menggunakan for loop untuk mencetak seluruh nilai dari Array:

int nilai[10] = {80, 89, 88, 87, 86, 85, 84, 73, 92, 91};

for (int i = 0; i < 10; i++) {
printf("%d ", nilai[i]);
}

// Output
// 80 89 88 87 86 85 84 73 92 91

Kita dapat memanfaatkan sizeof() untuk mengetahui jumlah elemen dari array tanpa perlu menghitungnya secara manual.

sizeof() mengembalikan ukuran dalam satuan Byte. Karena array tersimpan berurutan, kita dapat membagi ukuran seluruh array dengan ukuran satu elemen:

int nilai[10] = {80, 89, 88, 87, 86, 85, 84, 73, 92, 91};

printf("Ukuran total : %zu\n", sizeof(nilai)); // 40
printf("Ukuran elemen: %zu\n", sizeof(nilai[0])); // 4

size_t jumlah = sizeof(nilai) / sizeof(nilai[0]);
printf("Jumlah elemen: %zu\n", jumlah); // 10

Dengan begitu batas loop-nya pun dapat ditulis otomatis:

for (size_t i = 0; i < sizeof(nilai) / sizeof(nilai[0]); i++) {
printf("%d ", nilai[i]);
}

// Output
// 80 89 88 87 86 85 84 73 92 91
warning

Mengakses index array yang elemennya kosong atau melebihi ukuran arraynya menyebabkan undefined behaviour.

Relationship between arrays and pointers​

Singkatnya nama variable array adalah alamat dari elemen pertama array tersebut.

Mari kita misalkan kita memiliki Array berikut:

int nilai[5] = {70, 85, 90, 75, 88};
nilai:nilai[0]700x1000nilai[1]850x1004nilai[2]900x1008nilai[3]750x100Cnilai[4]880x1010

Ketika kita menulis nilai tanpa indeks, yang kita dapatkan bukanlah seluruh isi Array, melainkan alamat dari elemen pertamanya. Kita dapat membuktikannya dengan mencetak keduanya menggunakan %p:

printf("%p\n", nilai);
printf("%p\n", &nilai[0]);

Output keduanya akan sama, karena berlaku:

nilai == &nilai[0]

Seperti pointer p yang menunjuk ke N pada awal materi, nilai menunjuk ke nilai[0]. Bedanya, nilai menunjuk ke deretan nilai yang tersimpan berurutan, bukan ke satu nilai saja.

Indexing sebenarnya adalah Pointer Arithmetic​

Karena nilai adalah alamat awal, notasi nilai[i] sebenarnya hanyalah cara singkat dari operasi pointer:

nilai[0] == *(nilai + 0) // 70
nilai[1] == *(nilai + 1) // 85
nilai[2] == *(nilai + 2) // 90
nilai[3] == *(nilai + 3) // 75
nilai[4] == *(nilai + 4) // 88

nilai[i] artinya "nilai yang berada i langkah setelah alamat awal". indexing dan pointer arithmetic adalah dua notasi untuk hal yang sama.

Inilah juga alasan indeks dimulai dari 0: offset 0 berarti tidak bergeser, tepat di elemen pertama.

Perhatikan bahwa + 1 di sini bukan berarti maju 1 Byte, melainkan maju 1 elemen. Karena nilai bertipe int*, compiler tahu setiap langkah berukuran sizeof(int) yaitu 4 Byte. Jika tipenya char, satu langkah berukuran 1 Byte. Tipe data dari pointer menentukan besar langkahnya, walaupun ukuran pointer itu sendiri selalu sama.

Sebagai contoh kalian dapat melakukan:

*(nilai + 1) = 100; //Mengubah elemen di indeks ke 1 menjadi 100

// Mencetak elemen di dalam array
for (int i = 0; i < 5; i++) {
printf("%d ", *(nilai + i)); // sama seperti nilai[i]
}

Perbedaan penting: nama Array bukan variable​

Walaupun nama Array berperilaku seperti pointer, ia bukan variable. Ia tidak bisa di-assign atau digeser:

nilai = p; // Dilarang, nilai bukan variable
nilai = nilai + 2; // Dilarang, alamat awal Array bersifat tetap

Pointer Referencing to Array​

Kita dapat membuat sebuah variable pointer yang menyimpan alamat dari elemen pertama array. Sebagai contoh:

int *p = nilai;
pnilai:nilai[0]nilai[1]nilai[5].   .   .

Sekarang variable p dapat memiliki behaviour yang sama seperti array.

Karena p menyimpan alamat elemen pertama, yaitu alamat yang sama dengan nilai, maka semua cara akses yang bisa kita lakukan terhadap nilai juga bisa kita lakukan terhadap p.

p dapat di-index​

Sama seperti array, kita bisa menulis p[i] untuk mengakses elemen ke-i:

int nilai[5] = {70, 85, 90, 75, 88};
int *p = nilai;

printf("%d\n", p[0]); // 70, sama seperti nilai[0]
printf("%d\n", p[3]); // 75, sama seperti nilai[3]

p[i] berarti "nilai yang berada i langkah setelah alamat yang disimpan p", persis seperti nilai[i].

p dapat di-dereference​

Sama seperti pointer pada umumnya, kita bisa membuka isinya dengan *:

printf("%d\n", *p); // 70, sama seperti nilai[0]

p dapat mengubah isi array​

Karena p dan nilai menunjuk ke memori yang sama, perubahan lewat p langsung terlihat juga di nilai:

p[1] = 95;

printf("%d\n", nilai[1]); // 95
// Array sekarang = {70, 95, 90, 75, 88}

Begitu pun sebaliknya, mengubah nilai[1] akan mengubah p[1].

p dapat digeser, berbeda dengan nama array​

Ini kelebihan p dibanding nama array. Pada bagian sebelumnya kita tidak bisa menulis nilai++, tetapi variable pointer p boleh:

p++; // p sekarang maju satu elemen, menunjuk ke nilai[1]

printf("%d\n", *p); // 95
printf("%d\n", p[0]); // 95, indeks 0 sekarang dihitung dari posisi baru
printf("%d\n", p[-1]); // 70, boleh mundur karena kita masih di dalam array

Dengan menggeser p, kita memindahkan "titik awal" penghitungan indeks. Inilah alasan p dapat dipakai untuk menelusuri array sedikit demi sedikit.

Menelusuri array dengan p​

Sebagai contoh kalian dapat melakukan:

int nilai[5] = {70, 85, 90, 75, 88};
int *p = nilai;

for (int i = 0; i < 5; i++) {
printf("%d ", p[i]);
}

// Output
// 70 85 90 75 88
warning

Ada satu hal yang tidak diwarisi p dari array: pengetahuan tentang panjang array. Array adalah memori utuh, sedangkan p hanya menyimpan satu alamat:

printf("%zu\n", sizeof(nilai)); // 20, ukuran seluruh array (5 x 4)
printf("%zu\n", sizeof(p)); // 8, ukuran pointer saja (sistem 64-bit)

Karena itu p tidak tahu kapan ia melewati batas array, dan loop yang memakai p harus diberi batas secara eksplisit. Mengakses p[5] atau menggeser p terlalu jauh tetap menyebabkan undefined behavior.

tip

Ringkasan perilaku p vs nilai:

Perilakunilai (nama array)p (variable pointer)
Di-index p[i] / nilai[i]YaYa
Di-dereference *pYa (*nilai)Ya
Mengubah isi arrayYaYa
Di-assign ulang (= ...)TidakYa
Digeser (++, +=)TidakYa
sizeof = ukuran arrayYaTidak, hanya ukuran pointer

Multidimensional Array​

Selain array satu dimensi seperti daftar nilai yang sudah kita pelajari di atas, bahasa C juga mendukung array multidimensi. Tipe yang paling sering digunakan adalah array dua dimensi (2D), yang dapat dibayangkan seperti sebuah tabel atau matriks yang memiliki baris (row) dan kolom (column).

Deklarasi dan Inisialisasi​

Deklarasi array 2D sangat mirip dengan array biasa, kita hanya perlu menambahkan kurung siku kedua yang merepresentasikan kolom:

dataType namaArray[jumlahBaris][jumlahKolom];

Sebagai contoh, kita akan membuat sebuah matriks berukuran 2 baris dan 3 kolom:

int matriks[2][3] = {
{1, 2, 3}, // Elemen baris indeks ke-0
{4, 5, 6} // Elemen baris indeks ke-1
};

Mengakses Elemen Matriks​

Sama seperti array 1D, perhitungan indeks di array multidimensi juga dimulai dari 0. Untuk mengakses angka 6 pada matriks di atas (berada di baris ke-1, kolom ke-2), kita menuliskannya seperti ini:

printf("%d\n", matriks[1][2]); // Output: 6

Untuk mencetak seluruh isi dari matriks tersebut, kita umumnya menggunakan nested loop (loop bersarang) untuk menelusuri baris demi baris, lalu kolom demi kolom:

for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", matriks[i][j]);
}
printf("\n"); // Baris baru setelah satu baris matriks selesai dicetak
}

// Output:
// 1 2 3
// 4 5 6

String (Array of Characters)​

Berbeda dengan bahasa pemrograman modern seperti Python atau Java, bahasa C tidak memiliki tipe data string bawaan. Di dalam C, sebuah teks atau kalimat utuh pada dasarnya diperlakukan sebagai array of characters (array dari tipe data char).

Karakter Null Terminator (\0)​

Ada satu aturan mutlak yang membedakan string dari array karakter biasa: setiap string harus selalu diakhiri dengan karakter spesial yang disebut Null Terminator (\0). Karakter ini bertugas memberi tahu komputer di mana persisnya teks tersebut berakhir di dalam memori.

Deklarasi String​

Ada dua cara utama untuk mendeklarasikan string di C:

1. Cara Manual (Karakter per karakter) Kita harus memasukkan setiap huruf satu per satu dan menyertakan karakter \0 di akhir array secara manual.

char nama[5] = {'B', 'u', 'd', 'i', '\0'};

2. Cara Praktis (Menggunakan tanda kutip ganda " ") Ini adalah cara yang lebih ringkas dan disarankan. Saat menggunakan kutip ganda, compiler C akan secara otomatis menyisipkan karakter \0 di belakang string tanpa perlu kita tulis.

char nama[] = "Budi";
note

Meskipun kata "Budi" hanya terdiri dari 4 huruf, komputer sebenarnya memesan 5 elemen untuk array nama tersebut (4 huruf + 1 null terminator). Selalu perhitungkan ruang ekstra ini jika Anda mendeklarasikan panjang array string secara spesifik (misalnya char nama[5] = "Budi";).

Input dan Output String​

Untuk mencetak (output) dan membaca (input) sebuah string, kita menggunakan format specifier %s.

#include <stdio.h>

int main() {
char kota[20];

printf("Masukkan nama kota: ");
scanf("%s", kota); // Tidak perlu '&' untuk array karakter!

printf("Kota yang Anda masukkan adalah: %s\n", kota);
return 0;
}
tip

Ingat Kembali Materi Pointer! Pada contoh scanf di atas, mengapa kita tidak menggunakan tanda ampersand & di depan kota? Jawabannya ada pada materi hubungan pointer dan array sebelumnya: nama array (seperti kota) secara default sudah mewakili alamat awal memori (pointer) dari elemen pertamanya.