Pointer dan Linked List

Saat program dijalankan, setiap variabel disimpan di alamat tertentu dalam memori komputer. Alamat-alamat variabel tersebut bisa kalian simpan dalam variabel bertipe pointer. Pointer adalah tipe data yang dikhususkan untuk menyimpan alamat variabel lain. 

Kalian tidak bisa memberi nilai konstanta berupa angka atau string literal sebelum menunjuk alamat suatu variabel, kecuali jika kalian menggunakan alokasi memori dengan new atau malloc. Pemberian nilai pointer secara langsung bisa mengakibatkan munculnya error "segmentation fault" karena akses memori ilegal.

Deklarasi pointer sama dengan deklarasi tipe data lain. Bedanya, ada tanda asterisk (*) di antara nama variabel dan tipe data. Sintaksnya bisa kalian lihat di bawah ini.

  • Tipe_data * Nama_variabel
  • Tipe_data * Nama_variabel = &variabel_yang_ditunjuk
  • Tipe_data * Nama_variabel = variabel_array
  • Tipe_data * Nama_variabel = variabel_pointer

Untuk menyimpan alamat variabel selain array, kalian perlu tanda ampersand (&). Tanda ampersand digunakan untuk mengambil alamat variabel. Kalian bisa menyimpan alamat variabel saat inisiasi nilai pointer maupun setelahnya. Untuk menyalin alamat array dan pointer, kalian tidak memerlukan tanda ampersand.

Mencetak Alamat Variabel

Pointer hanya bisa digunakan untuk menyimpan alamat dari variabel dengan tipe data yang sama dengan tipe data pointer tersebut. Saat pointer menyimpan alamat variabel, kita bisa menyatakan bahwa "pointer tersebut menunjuk variabel (yang disimpan alamatnya)".

Contoh :

#include <iostream>

using namespace std;

int main(){
int var=12;
int *pointerku=&var;
    cout << "Nilai var : " << var << endl;
    cout << "Alamat var : " << &var << endl;
    cout << "Pointer : " << pointerku;
    return 0;
}
Output :
Nilai var : 12
Alamat var : 0x7ff41f8d98
Pointer : 0x7ff41f8d98

Kode program di atas mencetak alamat dari variabel var yang disimpan dalam pointerku. Alamat penyimpanan suatu variabel akan selalu berbeda setiap program dijalankan. Kalau kalian menerima pesan error, mungkin standar ISO C++ yang digunakan compiler kalian melarang pencetakan alamat pointer. Jika itu terjadi, abaikan saja contoh di atas dan lanjut ke contoh berikutnya.

Mencetak dan mengubah Nilai Variabel dengan Pointer

Berikutnya kita akan mencetak nilai variabel var dengan variabel pointer sebagai perantaranya. Untuk mencetak nilai variabel yang ditunjuk pointer, kalian cukup menambahkan tanda asterisk (*) sebelum nama variabel pointer.

#include <iostream>

using namespace std;

int main(){
int var=12;
int *pointerku=&var;
    cout << "*pointerku : " << *pointerku << endl;
    (*pointerku)++;
    cout << "(*pointerku)++ : "<< *pointerku;
    return 0;
}
Output :
*pointerku : 12
(*pointerku)++ : 13

Kode program di atas punya perbedaan dengan kode program untuk menampilkan alamat variabel. Salah satu perbedaannya ada penggunaan asterisk sebelum variabel pointerku di dalam cout. Karena variabel var yang ditunjuk pointerku bernilai 12, maka nilai yang dicetak adalah 12.

Operator untuk Pointer

Saat digunakan bersama dengan pointer, operator penjumlahan dan pengurangan berfungsi untuk menggeser alamat yang ditunjuk pointer. Ini akan berguna kalau kalian menggunakan array. Kalian tidak boleh menggunakan operator-operator tersebut untuk tipe data dasar yang hanya menyimpan satu nilai seperti bilangan bulat, pecahan desimal, dan karakter karena akan dianggap sebagai akses memori ilegal.

Contoh yang akan saya tunjukkan berikut ini menggunakan operator penjumlahan dan pengurangan untuk menggeser alamat "null terminated string" yang ditunjuk pointer.

#include <iostream>

using namespace std;

int main(){
char var[]="Halo dunia!";
char *pointerku=var;
    pointerku+=3;
    cout << pointerku << endl;
	
    pointerku-=2;
    cout << pointerku << endl;
	
    pointerku=pointerku-1;
    cout << pointerku << endl;
	
    pointerku=pointerku+5;
    cout << pointerku << endl;
	
    return 0;
}

Untuk menyimpan alamat array, kalian tidak perlu tanda ampersand. Kalian juga tidak perlu menggunakan tanda ampersand untuk menyalin isi satu pointer ke pointer lain. Tanda ampersand hanya digunakan untuk tipe data dasar dan struct.

Selain operator-operator pada contoh kode program di atas, kalian juga bisa menggunakan operator increment (++) dan decrement (--).

#include <iostream>
#include <string.h>

using namespace std;

int main(){
char var[256]="";
char *pointerku=var;
    cout << "string : ";gets(var);
    for(int i=0;i<strlen(var);i++){
    	cout << pointerku << endl;
    	pointerku++;
    }
	
    return 0;
}

Contoh di atas menggunakan fungsi strlen untuk mendapatkan panjang string. Karena itu, kalian perlu menambahkan "#include <string.h>" yang menyimpan function tersebut. Selain itu, kode program tersebut juga menggunakan fungsi gets() untuk mendapatkan input berupa satu baris string.

Ada dua loop pada contoh kode program di atas. Di dalam loop pertama, kalian bisa melihat operator ++ yang menggeser posisi awal string yang akan dicetak. Setelah itu, dalam loop kedua kalian bisa melihat operator -- yang membalikkan alamat string sebanyak satu langkah di setiap putaran. Loop kedua tidak akan bisa kalian tanpa loop kedua, kecuali kalau kalian menggeser pointernya ke akhir string terlebih dahulu.

Saat kalian tidak menggunakan pointer, untuk menghasilkan pola yang sama kalian perlu menyalin isi string ke variabel string lain yang mungkin akan memperlambat program jika stringnya cukup panjang. Hal itu bisa terjadi karena function menyalin string per karakter. Ini berbeda dengan saat kalian hanya menggeser pointer stringnya.

Selain string yang merupakan array karakter, kalian bisa melakukannya pada variabel array dengan tipe data lain. Kalian bisa melakukan eksplorasi sendiri dengan resiko error saat program dijalankan karena akses memori ilegal.

Pointer Member Struct dan Union

Struct dan union bisa disalin alamatnya dengan cara yang sama dengan tipe data dasar. Tapi, itu tidak berlaku saat pencetakan dan pemberian nilai membernya. 

Kalian tidak memerlukan tanda asterisk sebelum nama variabel untuk mengubah nilai member struct atau union yang ditunjuk pointer. Kalian bisa menggunakan operator "->" untuk menggantikan tanda titik (.) jika kalian mengakses isi dari member struct atau union melalui pointer. Contoh kode programnya bisa kalian lihat di bawah ini.

#include <iostream>

using namespace std;

struct data_siswa{
	string nama;
	string alamat;
}siswa;

int main(){
data_siswa *pointerku=&siswa;
    pointerku->nama="Gerimisya Stela Rida";
    pointerku->alamat="Bumi";
    cout << "Nama : " << pointerku->nama << endl;
    cout << "Alamat : " << pointerku->alamat;
    return 0;
}

Contoh di atas menggunakan struct. Tapi, operator dan pola penulisan yang sama juga berlaku untuk union.

Linked List

Pointer bisa digunakan untuk membuat linked List. Linked List adalah sekumpulan data yang saling terhubung dengan menggunakan pointer. Kalian bisa membuat linked list dengan struct atau tipe data sejenis.

Linked list minimal butuh satu member bertipe pointer yang digunakan sebagai "penunjuk" atau link di dalam struct. Berdasarkan jumlah linknya, linked bisa dikelompokkan menjadi 3.

  • Singly linked list : linked list dengan satu link.
  • Doubly linked list : linked list dengan dua link yang menunjuk data sebelum dan sesudahnya.
  • Multiply linked list : Linked list yang terdiri dari banyak link. Link biasanya disimpan dengan array.
Linked list linear yang terhubung kedua ujungnya disebut dengan Circular linked list. Selain itu, ada linked list bercabang yang disebut dengan tree. Kalian juga bisa membuat graph dan beberapa struktur data lain dengan linked list.

Linked list bisa diimplementasikan dengan struct yang berisi pointer dan beberapa member lain. Contoh struct yang digunakan sebagai linked list adalah seperti di bawah ini.

struct dataku{
    int data1;
    string data2;
    dataku *next;
    dataku *prev;
}

Contoh di atas menggunakan dua link yang bernama next dan prev. Struct tersebut biasa digunakan untuk doubly linked list. Singly linked list cuma butuh satu link. Link pada singly linked list bisa menunjuk ke data setelahnya atau data sebelumnya. Itu berbeda dengan doubly linked list yang masing-masing linknya menunjuk ke arah yang berbeda.

Penerapan singly linked list bisa kalian lihat di bawah ini.

#include <iostream>

using namespace std;

struct dataku{
    string nama;
    string alamat;
    dataku *setelahnya;
};

struct info_data {
    dataku *depan;
    dataku data[10];
};

void tampilkan_data(info_data *info){
    dataku *tdata=info->depan;
    while(tdata!=0){
    	cout << tdata->nama << endl;
    	tdata=tdata->setelahnya;
    }
}

void pop_data(info_data *info){
    dataku *pdata=info->depan;
    //hapus alokasi memori pdata
    info->depan=pdata->setelahnya;
}

int main(){
    info_data info;
    dataku *dt=info.data;
    //inisiasi info_data
    info.depan=0;
    
    //push data
    dt[0].nama="Sani";
    dt[1].nama="Geri";
    dt[2].nama="Toni";
    dt[3].nama="Tono";
    dt[4].nama="Tino";
    
    info.depan=&dt[4];
    dt[4].setelahnya=&dt[3];
    dt[3].setelahnya=&dt[2];
    dt[2].setelahnya=&dt[1];
    dt[1].setelahnya=&dt[0];
    dt[0].setelahnya=0;

    //hapus data
    pop_data(&info);
    pop_data(&info);
    //tampilkan data
    tampilkan_data(&info);

    return 0;
}
Output :
Toni
Geri
Sani

Contoh di atas menggunakan array untuk membuat simulasi antrian / queue. Tapi , penerapan linked list yang sebenarnya tidak menggunakan array, melainkan alokasi memori untuk membuat data yang lebih dinamis. Satu-satunya yang benar-benar sesuai dengan penerapan sebenarnya hanya saat menampilkan data.