Konsep kelas dalam C++ ditujukan untuk menciptakan tipe data baru. Sebuah tipe terdiri dari kumpulan bit yang merepresentasikan nilai abstrak dari instansiasi tipe tersebut serta kumpulan operasi terhadap tipe tersebut. Sebagai contoh int adalah sebuah tipe karena memiliki representasi bit dan kumpulan operasi seperti \penambahan dua variabel bertipe int", \perkalian dua variabel bertipe int", dsb.
Dengan cara yang sama, sebuah kelas juga menyediakan sekumpulan operasi (biasanya public) dan sekumpulan data bit (biasanya non-public) yang menyatakan nilai abstrak objek dari kelas tersebut. Hubungan antara kelas dengan objek dapat dinyatakan dengan analogi berikut:
class vs. object = type vs. variable
Pendeklarasian kelas (terutama fungsi anggotanya) menentukan perilaku objek dalam operasi penciptaan, pemanipulasian, pemusnahan objek dari kelas tersebut. Dalam pemrograman dengan bahasa yang berorientasi objek dapat dilihat adanya peran perancang kelas dan pengguna kelas. Perancang kelas menentukan representasi internal objek yang berasal kelas yang dirancangnya.
Pendeklarasian kelas dilakukan seperti pendefinisian sebuah struktur namun dengan mengganti kata kunci struct dengan class. Kata kunci class dalam C++ dapat dipandang sebagai perluasan dari kata kunci struct, hanya perbedaannya nama kelas (tag-name) dalam C++ sekaligus merupakan tipe baru.
Contoh: Sebuah struct yang memiliki prosedur/fungsi
CODE
1 struct Stack { // nama tag "Stack" sekaligus menjadi tipe baru
2 /*******************
3 * function member *
4 *******************/
5 void Pop(int&);
6 void Push (int);
7 int isEmpty();
8 // ... definisi fungsi lainnya
9
10 /***************
11 * data member *
12 ***************/
13 int topStack;
14 int *data;
15 // ... definisi data lainnya
16 };
Sebuah kelas memiliki satu atau lebih member (analog dengan field pada struct). Ada dua jenis member:
* Data member, yang merupakan representasi internal dari kelas
* Function member, kumpulan operasi (service/method) yang dapat diterapkan terhadap objek, seringkali disebut juga sebagai class interface
Setiap field yang dimiliki sebuah struct dapat secara bebas diakses dari luar struktur tersebut. Hal ini berbeda dibandingkan dengan pengaksesan terhadap anggota kelas. Hak akses dunia luar terhadap anggota (data dan fungsi) diatur melalui tiga kata kunci private, public, dan protected. Setiap fungsi anggota kelas selalu dapat mengakses data dan fungsi anggota kelas tersebut (dimanapun data tersebut dideklarasikan: private, public, protected).
Sedangkan fungsi bukan anggota kelas hanya dapat mengakses anggota yang berada di bagian public. Hak akses terhadap fungsi dan data anggota kelas dinyatakan sebagai berikut:
* public
dapat diakses oleh fungsi di luar kelas (fungsi bukan anggota kelas tersebut) dengan menggunakan operator selektor (. atau ->)
* private
hanya dapat diakses oleh fungsi anggota kelas tersebut
* protected
hanya dapat diakses oleh fungsi anggota kelas tersebut dan fungsi-fungi anggota kelas turunan
Dalam deklarasi \kelas" Stack pada Contoh diatas, semua anggota bersifat public, karena hal ini sesuai dengan sifat sebuah struct. Namun jika, kata kunci \struct" diganti menjadi \class", maka semua anggota otomatis bersifat private.
Dalam contoh tersebut, fungsi-fungsi anggota Pop(), Push(), dsb hanya dideklarasikan namun belum didefinisikan. Pendefinisian anggota fungsi dapat dilakukan dengan dua cara:
* Di dalam class body, otomatis menjadi inline function
* Di luar class body, nama fungsi harus didahului oleh class scope
Contoh: Deklarasi kelas Stack beserta fungsi anggota
CODE
1 class Stack {
2 public:
3 // function member
4 void Pop(int& ); // deklarasi (prototype)
5 void Push (int); // deklarasi (prototype)
6 /*--- pendefinisian di dalam class body ---*/
7 int isEmpty() {
8 return topStack == 0;
9 }
10 private:
11
12 // data member
13 int topStack; /* posisi yang akan diisi berikutnya */
14 int *data;
15 }; // PERHATIKAN TITIK KOMA !!!
16
17 // pendefinisian member function Pop di luar
18 // class body
19 void Stack::Pop(int& item) {
20 if (isEmpty()) {
21 // error message
22 }
23 else {
24 topStack--;
25 item = data [topStack];
26 }
27 } // TIDAK PERLU TITIK KOMA !!!
28
29 void Stack::Push (int item) {
30 if (isFull()) {
31 // error message
32 }
33 else {
34 data [topStack] = item;
35 topStack++;
36 }
37 }
Pointer implisit this
Setiap objek dari suatu kelas memiliki sendiri salinan anggota data dari kelas tersebut. Namun, hanya ada satu salinan anggota fungsi untuk objek-objek dari kelas tersebut. Dengan kata lain, jika ada dua objek dari suatu kelas yang memanggil salah satu fungsi anggota kelas tersebut maka kedua objek akan menjalankan rangkaian instruksi yang terletak pada lokasi memori yang sama, tetapi anggota data yang diakses oleh fungsi anggota tersebut terletak pada dua lokasi memori yang berbeda.
Untuk menangani hal di atas, setiap function member secara implisit memperoleh argumen (parameter aktual) tersembunyi berupa pointer ke objek (implicit this pointer). Jika pointer this ini akan digunakan di dalam fungsi anggota Push di atas, setiap pengaksesan terhadap data anggota (maupun fungsi anggota) kelas Stack dapat diawali dengan `this->'.
CODE
1 void Stack::Push (int item) {
2 // . . .
3 this->data [this->topStack] = item;
4 this->topStack++;
5 // . . .
6 }
Dalam contoh berikut di atas, perhatikan bahwa parameter formal item tidak dapat dituliskan sebagai this->item karena bukan merupakan anggota kelas Stack.
Pointer this merupakan sebuah rvalue sehingga ekspresi assignment terhadap this dalam contoh berikut tidak diijinkan:
this = ...; // ruas kanan diisi suatu ekpresi
Mengapa ada this pointer?
* Pointer implisit this untuk kelas X, dideklarasikan sebagai X* this, dan digunakan untuk mengakses member di dalam kelas tersebut
* Pointer this dapat juga digunakan memberikan return value yang berjenis kelas tersebut (misalnya fungsi operator)
Objek dari Kelas
Pendeklarasian kelas tidak mengakibatkan alokasi memory untuk kelas tersebut. Memory dialokasikan jika ada objek yang didefinisikan dengan tipe kelas tersebut.
Dengan menggunakan kelas Stack di atas, berikut ini beberapa contoh pendefinisian variabel (objek) yang berasal dari kelas Stack di atas:
CODE
1 Stack myStack;
2 Stack OprStack [10];
3 Stack * pts = new Stack;
4 Stack ns = myStack; // definition & initialization
5
6 // inisialisasi di atas sama dengan instruksi berikut:
7 // ns.topStack = myStack.topstack
8 // ns.data = myStack.data
9 int x;
10
11 // constructor Stack harus sudah menjamin inisialisasi stack
12 // dengan benar
13
14 myStack.Push (99);
15 OprStack[2].Pop(x);
16 pts->Push(x);
17
18 if (myStack.isEmpty()) {
19 printf ("Stack masih kosong . . . ");
20 }
Pengaksesan public member
Anggota yang publik dapat diakses melalui objek seperti layaknya pengaksesan field pada sebuah struct. Pengaksesan terhadap data member: jika berperan sebagai lvalue maka berarti diacu alamatnya, dan jika berperan sebagai rvalue maka berarti diacu isinya. Pengaksesan terhadap function member berarti pemanggilan terhadap fungsi tersebut.
Constructor, Destructor, dan Copy Constructor
Untuk tipe-tipe primitif (int, float, char, double, dsb.) kompilator mengetahui bagaimana mengalokasikan, menginisialisasi, dan mendealokasikan kumpulan bit yang merepresentasikan tipe tersebut. Untuk tipe data yang lebih kompleks, proses ini mungkin harus dilakukan sendiri oleh perancang kelas. Untuk keperluan tersebut, C++ menggunakan konsep constru-
ctor dan destructor. Untuk selanjutnya, dalam penulisan \ctor" akan digunakan untuk menyatakan constructor dan \dtor" untuk menyatakan destructor.
Constructor (destructor) adalah fungsi anggota (khusus) yang secara otomatis dipanggil pada saat penciptaan (pemusnahan) objek. Dalam sebuah kelas, ctor dan dtor adalah fungsi yang memiliki nama yang sama dengan nama kelas. Sebuah kelas mungkin tidak memiliki ctor atau memiliki lebih dari satu ctor. Tugas utama konstruktor adalah untuk menginisialisasi nilai-nilai dari anggota data yang dimiliki kelas. Konstruktor dapat dibedakan menjadi dua jenis:
1. Default constructor: konstruktor yang menginisialisasi objek dengan nilai(-nilai) default yang ditentukan oleh perancang kelas. Dalam deklarasi kelas, ctor ini tidak memiliki parameter formal.
2. User-defined constructor: konstruktor yang menginisialisasi objek dengan nilai(-nilai) yang diberikan oleh pemakai kelas pada saat objek diciptakannya. Dalam deklarasi kelas, ctor ini memiliki satu atau lebih parameter formal.
Destructor adalah fungsi yang namanya sama dengan nama kelas dan didahului tanda `~' (tilde). Sebuah kelas dapat memiliki paling banyak satu destructor
Sebuah objek dapat pula diciptakan dengan cara menginisialisasinya dengan objek lain yang sudah ada. Dalam hal ini, objek tersebut akan diciptakan melalui copy constructor. Untuk selanjutnya \cctor" akan digunakan untuk menyatakan copy constructor.
CODE
Stack ns = myStack; // create & init
Dengan cara di atas, inisialisasi objek dilakukan oleh "default cctor" yang melakukan bitwise copy. Hal ini dapat mengakibatkan kesalahan untuk kelas yang memiliki anggota data berupa pointer. Dengan contoh kelas Stack yang diberikan pada Contoh diatas, anggota data dari objek ns dan myStack akan mengacu ke lokasi memori yang sama, padahal kedua objek tersebut seharusnya tidak memiliki lokasi memori yang sama.
Jika \default cctor" tidak dikehendaki, perancang kelas harus mendefinisikan sebuah copy constructor.
Dalam penulisan kode sebuah kelas akan terdapat dua bagian berikut:
1. Interface / specification yang merupakan deklarasi kelas, dan
2. Implementation / body yang berisi definisi dari fungsi-fungsi anggota dari kelas tersebut.
Agar kelas dapat digunakan oleh pengguna, hanya bagian deklarasi kelas yang perlu disertakan di dalam program pengguna. Untuk itu, deklarasi kelas dituliskan ke dalam file X.h (X adalah nama kelas). Untuk mencegah penyertaan header lebih dari satu kali, deklarasi kelas dituliskan di antara #ifdef XXXX H dan #endif (atau #endif XXXX H).
CODE
1 /*---------------------------------------*
2 * Nama file: Stack.h *
3 * Deskripsi: interface dari kelas Stack *
4 *---------------------------------------*/
5 #ifndef STACK_H
6 #define STACK_H
7 class Stack {
8 public:
9 // ctor -- dtor
10 Stack(); // constructor
11 Stack (int); // constructor dengan ukuran stack
12 ~Stack(); // destructor
13
14 // fungsi-fungsi layanan
15 void Pop(int&);
16 void Push (int);
17 int isEmpty()
18 { // pendefinisian di dalam class body
19 return topStack == 0;
20 }
21 private:
22 // data member
23 const int defaultStackSize = 500; // ANSI: tidak boleh inisialisasi
24 int topStack;
25 int size;
26 int *data;
27 };
28 #endif STACK_H
CODE
1 /*-------------------------------------------------*
2 * Nama file: Stack.cc *
3 * Deskripsi: definisi function members dari kelas *
4 * Stack (implementation) *
5 *-------------------------------------------------*/
6 #include
7
8 // Stack constructor
9 Stack::Stack () {
10 data = new int [defaultStackSize];
11 topStack = 0;
12 size = defaultStackSize;
13 }
14
15 // constructor dengan ukuran stack
16 Stack::Stack (int s) { /* parameter s = ukuran stack */
17 data = new int [s]; /* alokasi array integer dengan
18 * index 0 .. s-1 */
19 topStack = 0;
20 size = s;
21 }
22
23 Stack::~Stack () { // destructor
24 delete [] data; // dealokasi array integer
25 size = 0;
26 data = 0;
27 }
28
29 void Stack::Pop(int& item) {
30 if isEmpty()
31 // error message
32 else {
33 topStack--;
34 item = data [topStack];
35 }
36 }
37
38 void Stack::Push (int item) {
39 // . . .
40 data [topStack] = item;
41 topStack++;
42 // . . .
43 }
CODE
1 /*---------------------------*
2 * Nama file: main.cc *
3 * Deskripsi: Uji coba stack *
4 *---------------------------*/
5 #include
6 #include ... // header file lain yang diperlukan
7
8 main ()
9 {
10 // kamus
11 Stack s1; // constructor Stack()
12 Stack s2 (20); // constructor Stack (int)
13
14 // algoritma ...
15 // kode program dituliskan di sini
16 }
Implementasi (definisi fungsi-fungsi anggota) seperti yang terlihat pada Contoh diatas dituliskan ke dalam file X.cc, X.cpp, X.cxx atau file X.C.
Dalam contoh pada Contoh ditas, cctor tidak didefinisikan sehingga jika dilakukan penciptaan objek lewat inisialisasi, data member data dari dua objek yang berbeda akan menunjuk ke lokasi yang sama. Selain itu, contoh kelas tersebut juga mendefinisikan dua konstruktor: satu default constructor (Stack::Stack()) dan satu konstruktor yang memungkinkan pemakai kelas Stack menyatakan ukuran maksimum stack yang akan digunakannya (Stack::Stack (int)).
Perhatikanlah pula bahwa ctor, dtor, maupun cctor merupakan fungsi yang tidak memiliki tipe kembalian (return type) karena fungsi-fungsi tersebut tidak dapat dipanggil secara eksplisit oleh pengguna, melainkan secara implisit oleh kompilator.
Penciptaan/Pemusnahan Objek
Setelah Stack.h didefinisikan dan Stack.cc dikompilasi menjadi Stack.o maka pengguna kelas dapat menuliskan program berikut yang kemudian dilink dengan Stack.o (atau melalui pustaka tertentu). Contoh program yang menggunakan kelas Stack di atas ditunjukkan pada Contoh diatas (main.cc).
Ada beberapa jenis objek yang dapat digunakan di dalam program C++:
* Automatic object: diciptakan jika ada deklarasi objek di dalam blok eksekusi dan dimusnahkan (secara otomatis oleh kompilator) pada saat blok yang mengandung deklarasi tersebut selesai eksekusi
* Static object: diciptakan satu kali pada saat program dimulai dan dimusnahkan (secara otomatis oleh kompilator) pada saat program selesai
* Free store object: diciptakan dengan operator new dan dimusnahkan dengan operator delete. Kedua hal ini harus secara eksplisit dilakukan oleh pengguna kelas/objek.
* Member object: sebagai anggota dari kelas lain penciptaannya dilakukan melalui memberwise initialization list.
Contoh ini menunjukkan sebuah penggalan program yang berisi tiga dari empat jenis objek di atas.
CODE
1 #include
2
3 Stack s0; /* global (static) */
4
5 int reverse() {
6 static Stack tstack = ...; /* local static */
7
8 // kode untuk fungsi reverse() di sini
9 }
10
11 main () {
12 Stack s1; // automatic
13 Stack s2 (20); // automatic
14 Stack *ptr;
15
16 ptr = new Stack(50); /* free store object */
17 while (...) {
18 Stack s3; // automatic
19
20 /* assignment dgn automatic object */
21 s3 = Stack (5); // ctor Stack(5) is called
22 /* dtor Stack(5) is called */
23
24 // ... instruksi lain ...
25 }
26 /* dtor s3 is called */
27
28 delete ptr; /* dtor *ptr is called */
29 }
30 /* dtor s2 is called */
31 /* dtor s1 is called */
32
33 /* dtor s0 is called */
* Array of Objects
Untuk memberikan kemungkinan pada pemakai kelas mendeklarasikan array dari objek, kelas tersebut harus memiliki constructor yang dapat dipanggil tanpa argumen (default constructor).
Jika array diciptakan melalui operator new, destructor harus dipanggil (melalui delete) untuk setiap elemen array yang dialokasikan.
CODE
1 #include
2
3 Process *pa1, *pa2;
4
5 pa1 = new Process [3];
6 pa2 = new Process [5];
7
8 // ... kode yang menggunakan pa1 & pa2
9
10 delete pa1; // not OK
11 delete [] pa2; // OK
3.6 Copy Constructor
Pada bagian sebelumnya telah dijelaskan sekilas mengenai salah satu manfaat dari cctor. Pada bagian ini akan dijelaskan lebih lanjut manfaat lain dari cctor. Perhatikan Contoh dibawah
CODE
1 #include
2
3 void f1 (const Stack& _) { /* instruksi tidak dituliskan */}
4
5 void f2 (Stack _) { /* instruksi tidak dituliskan */ }
6
7 Stack f3 (int) {
8 /* instruksi tidak dituliskan */
9 return ...; // return objek bertipe "Stack"
10 }
11
12 main ()
13 {
14 Stack s2 (20); // constructor Stack (int)
15
16 /* s3 diciptakan dengan inisialisasi oleh s2 */
17 Stack s3 = s2; // BITWISE COPY, jika
18 // tidak ada cctor yang didefinisikan
19 f1 (s2); // tidak ada pemanggilan cctor
20 f2 (s3); // ada pemanggilan cctor
21 s2 = f3 (-100); // ada pemanggilan cctor dan assignment
22 }
Dengan menggunakan deklarasi Stack.h yang sudah diberikan pada bagian tersebut, penciptaan objek s3 melalui inisialisasi oleh s1 akan mengakibatkan terjadinya proses penyalinan bit perbit dari objek s1 ke s3. Hal ini terjadi karena kelas Stack belum memiliki copy constructor.
Dalam kelas Stack seperti yang terlihat pada Contoh stack.h, proses penyalinan bit per bit ini akan mengakibatkan efek yang tidak diinginkan. Nilai dari data anggota topStack dan size dapat disalin bit per bit tanpa masalah, tetapi jika nilai data anggota data disalin bit per bit akan terjadi adanya dua objek berjenis Stack yang mengacu ke lokasi memori yang sama, padahal seharusnya keduanya mengacu ke lokasi memori yang berbeda. Untuk menghindari hal ini, kelas Stack harus mendefinisikan sebuah copy constructor.
Copy constructor (cctor) dipanggil pada saat penciptaan objek yang dilakukan melalui:
* Deklarasi variabel dengan inisialisasi
* Pemberian parameter aktual ke parameter formal yang dilakukan secara \pass by value". Dalam Contoh diatas, parameter aktual s2 diberikan ke fungsi fi f1() tanpa adanya pemanggilan copy constructor sedangkan parameter aktual s3 diberikan ke fungsi f2() dengan adanya pemanggilan copy constructor.
* Pemberian nilai kembalian fungsi yang nilai kembaliannya bertipe kelas tersebut (bukan pointer atau reference). Dalam Contoh diatas hal ini terjadi pada saat instruksi return dijalankan, bukan pada saat nilai kembalian diassign ke variabel s2.
Copy constructor untuk kelas MyClass dideklarasikan sebagai fungsi dengan nama MyClass dan memiliki sebuah parameter formal berjenis const reference dari kelas MyClass.
CODE
MyClass(const MyClass&);
Parameter aktual yang diberikan pada saat eksekusi adalah objek (yang akan diduplikasi) yang digunakan untuk menginisialisasi objek yang sedang diciptakan oleh cctor.
Deklarasi kelas Stack harus ditambahkan dengan deklarasi cctor yang sesuai seperti terlihat pada Contoh berikut:
CODE
1 class Stack {
2 public:
3 Stack(); // constructor
4 Stack (int); // constructor dengan ukuran stack
5 Stack (const Stack&); // copy constructor
6 ~Stack(); // destructor
7 // ...anggota-anggota lain tidak dituliskan...
8 };
Yang harus dituliskan dalam definisi cctor adalah kode yang membuat penciptaan objek secara inisialisasi menjadi benar. Dalam contoh Stack di atas, yang harus dilakukan adalah mengalokasikan tempat untuk data member data agar setiap objek yang berasal dari kelas Stack memiliki lokasi memori terpisah untuk menyimpan datanya. Perhatikan Contoh berikut:
CODE
1 Stack::Stack (const Stack& s)
2 {
3 int i;
4
5 size = s.size;
6 topStack = s.topStack;
7 data = new int [size]; // PERHATIKAN: data member "data" harus di
8 // alokasi ulang, tidak disalin dari
9 // "s.data".
10 for (i=0; i
11 data[i] = s.data[i];
12 }
Untuk memahami pemanggilan ctor, cctor, dtor perhatikan bagian berikut. Misalkan pada deklarasi kelas Stack di tambahkan beberapa instruksi seperti terlihat pada Contoh berikut:
CODE
1 /* Nama file: Stack.h */
2 /* Deskripsi: ujicoba constructor, destructor, */
3
4 #ifndef STACK_H
5 #define STACK_H
6 class Stack {
7 public:
8 Stack() { printf ("ctor %x\n", this); }
9 Stack (const Stack&) { printf ("cctor %x\n", this); }
10 ~Stack() { printf ("dtor %x\n", this); }
11 };
12 #endif STACK_H
yang dipanggil oleh program pada Contoh berikut:
CODE
1 /*-----------------------------------------------*
2 * Nama file: test.cc *
3 * Deskripsi: pengamatan pemanggilan ctor, dtor, *
4 * cctor *
5 *-----------------------------------------------*/
6 #include
7
8 f (Stack _) {
9 printf ("ffffffff\n");
10 }
11
12 Stack ll;
13
14 main() {
15 printf ("11111111\n");
16 Stack x;
17 printf ("22222222\n");
18 Stack y = x;
19 printf ("33333333\n");
20 f(x);
21 printf ("44444444\n");
22 }
0 comments:
Post a Comment