![]() |
|
Marco Cantù's |
Bab 4
|
Notes: User-Defined Data Types dan beberapa istilah lain tetap menggunakan bahasa Inggris karena lebih singkat dan berarti.
Bersama dengan notasi type, satu dari sekian ide luar biasa yang diperkenalkan oleh bahasa Pascal adalah kemampuan untuk mendefinisikan data type baru dalam sebuah program. Pemrogram dapat mendefinisikan data type mereka sendiri dengan type constructor, seperti subrange types, array types, record types, enumerated types, pointer types, dan set types. Data type yang terpenting adalah class, yang merupakan bagian dari perluasan object-oriented dari Object Pascal, yang tidak diliput dalam buku ini.
Jika Anda berpikir bahwa type constructor adalah hal yang umum dalam sekian banyak bahasa pemrograman, Anda benar, tapi Pascal merupakan bahasa pertama yang memperkenalkan ide tersebut dalam cara yang formal dan sangat tepat. Masih ada beberapa bahasa dengan sekian banyak mekanisme untuk mendefinisikan type baru.
Type-type ini dapat diberikan sebuah nama untuk digunakan selanjutnya atau diterapkan ke suatu variabel secara langsung. Jika Anda memberikan nama pada suatu type, Anda harus memberikan bagian khusus dalam code, seperti berikut:
type // subrange definition Uppercase = 'A'..'Z'; // array definition Temperatures = array [1..24] of Integer; // record definition Date = record Month: Byte; Day: Byte; Year: Integer; end; // enumerated type definition Colors = (Red, Yellow, Green, Cyan, Blue, Violet); // set definition Letters = set of Char;
Konstruksi pendefinisian type yang serupa juga dapat digunakan secara langsung untuk mendefinisikan suatu variabel tanpa nama type yang eksplisit, seperti dalam code berikut:
var DecemberTemperature: array [1..31] of Byte; ColorCode: array [Red..Violet] of Word; Palette: set of Colors;
Note: Secara umum, Anda seharusnya menghindari penggunaan unnamed types (type tanpa nama) seperti terlihat pada contoh code diatas, karena Anda tidak dapat melewatkan mereka sebagai parameter ke suatu rutin atau mendeklarasikan variabel lain dengan type yang sama. Aturan Pascal mengenai type compatibility, sebenarnya, didasarkan pada nama type, bukan pada definisi type yang aktual. Dua variabel dari dua type yang identik tidaklah kompatibel, kecuali jika type yang mereka gunakan mempunyai nama yang persis sama, dan unnamed type diberikan nama internal oleh kompiler. Biasakan untuk mendefinisikan data type tiap kali Anda membutuhkan suatu variabel untuk struktur yang rumit, dan Anda tidak akan menyesalkan waktu yang Anda pergunakan untuk itu.
Tapi apa artinya definisi type tadi? Saya akan memberikan beberapa penjelasan kepada mereka yang tidak familiar dengan type construct dalam Pascal. Saya juga akan mencoba untuk menggarisbawahi perbedaan dari konstruksi yang sama dalam bahasa pemrograman lainnya, sehingga Anda mungkin tertarik untuk membaca bagian berikut ini walaupun Anda sudah familiar dengan definisi type seperti yang telah dicontohkan sebelumnya. Akhirnya, saya akan menunjukkan beberapa contoh dalam Delphi dan memperkenalkan beberapa tool yang memungkinkan Anda mengakses informasi type secara dinamis.
Suatu subrange type mendefinisikan suatu range nilai didalam suatu range dari type yang lain (sehingga dinamai subrange). Anda dapat mendefinisikan suatu subrange dari type Integer, dari 1 sampai 10 atau dari 100 sampai 1000, atau Anda dapat mendefisinikan suatu subrange dari type Char, seperti contoh berikut:
type Ten = 1..10; OverHundred = 100..1000; Uppercase = 'A'..'Z';
Dalam pendefinisian subrange, Anda tidak harus menyebutkan nama base type. Anda hanya perlu untuk menyediakan dua konstanta untuk type tersebut. Original type haruslah ber-type ordinal, dan resulting type adalah ordinal type yang lain.
Ketika Anda telah mendefinisikan suatu subrange, Anda dapat secara legal memberinya nilai diantara range tersebut. Kode seperti ini adalah benar:
var UppLetter: UpperCase; begin UppLetter := 'F';
Tapi yang ini tidak valid:
var UppLetter: UpperCase; begin UppLetter := 'e'; // compile-time error
Menuliskan kode seperti diatas akan menghasilkan suatu kesalahan saat melakukan kompilasi, "Constant expression violates subrange bounds." Seandainya Anda menuliskan kode seperti ini:
var UppLetter: Uppercase; Letter: Char; begin Letter :='e'; UppLetter := Letter;
Delphi akan melakukan kompilasi terhadap kode tersebut. Saat aplikasi dijalankan, jika Anda mengaktifkan pilihan kompilasi Range Checking (dalam Compiler page dari dialog box Project Options), Anda akan mendapatkan sebuah pesan Range check error.
Note: Saya sarankan Anda untuk mengaktifkan pilihan kompilasi ini ketika Anda sedang menulis aplikasi, sehingga aplikasi akan lebih stabil dan mudah untuk dilacak (debug), bilamana ada kesalahan seperti ini Anda akan mendapatkan pesan secara eksplisit dan tidak memberikan perilaku yang tidak menentu. Anda dapat menonaktifkan pilihan ini saat membangun aplikasi pada tahap akhir, untuk mempercepat jalannya aplikasi. Namun demikian, perbedaannya sangat sedikit, dan untuk alasan inilah saya menyarankan Anda untuk meninggalkan pilihan run-time check tetap diaktifkan, bahkan pada aplikasi tahap akhir. Hal yang sama juga berlaku untuk pilihan run-time check yang lain, seperti pemeriksaan overflow dan stack.
Enumerated types membentuk user-defined ordinal type yang lain. Daripada mengindikasi suatu range dari type yang sudah ada, dalam suatu enumeration Anda mencantumkan semua nilai kemungkinan untuk type tersebut. Dengan kata lain, suatu enumeration adalah suatu daftar nilai. Berikut ini adalah contohnya:
type Colors = (Red, Yellow, Green, Cyan, Blue, Violet); Suit = (Club, Diamond, Heart, Spade);
Tiap nilai dalam daftar tersebut mempunyai asosiasi ordinality, dimulai dari nol. Ketika Anda menjalankan fungsi Ord untuk suatu nilai dari enumerated type, Anda akan mendapatkan nilai kembali yang berdasarkan perhitungan dari nol. Misalnya, Ord (Diamond) mengembalikan nilai 1.
Note: Enumerated types dapat mempunyai beberapa representasi internal yang berbeda. Secara default, Delphi menggunakan representasi 8-bit, terkecuali bilamana ada lebih dari 256 nilai yang berbeda, dia akan menggunakan representasi 16-bit. Ada juga representasi 32-bit, yang mungkin berguna untuk kekompatibilitasan dengan library C atau C++. Anda dapat mengganti perilaku default, untuk meminta representasi yang lebih besar, dengan menggunakan direktif kompiler $Z.
VCL (Visual Component Library) dari Delphi menggunakan enumerated type dalam banyak tempat. Misalnya, style untuk border form didefinisikan seperti berikut:
type TFormBorderStyle = (bsNone, bsSingle, bsSizeable, bsDialog, bsSizeToolWin, bsToolWindow);
Ketika nilai suatu property adalah enumeration, Anda biasanya dapat memilih dari daftar nilai yang ditampilkan di Object Inspector, seperti terlihat pada Gambar 4.1.
Gambar 4.1: Suatu property ber-type enumerated dalam Object Inspector
File Help dari Delphi secara umum menampilkan nilai yang mungkin untuk suatu enumeration. Sebagai alternatif,, Anda dapat menggunakan program OrdType, tersedia di www.marcocantu.com, untuk melihat daftar nilai dari tiap enumeration, set, subrange, dan ordinal type lainnya dalam Delphi. Anda dapat melihat contoh keluaran dari program ini dalam Gambar 4.2.
Gambar 4.2: Informasi detil mengenai enumerated type, ditampilkan oleh program OrdType (tersedia di website saya).
Set types mengindikasikan sekelompok nilai, dimana daftar dari nilai yang dimungkinkan diindikasikan dengan ordinal type yang didasarkan oleh set tersebut. Ordinal type ini biasanya terbatas, dan seringkali direpresentasikan dengan sebuah enumeration atau sebuah subrange. Jika kita mengambil subrange 1..3, maka nilai yang dimungkinkan dari suatu set yang didasarkan pada subrange tersebut hanya menyertakan kemungkinan hanya 1, hanya 2, hanya 3, 1 dan 2, 1 dan 3, 2 dan 3, ketiga nilai tersebut, atau kosong.
Suatu variabel biasanya menyimpan satu dari nilai yang dimungkinkan dari range type-nya. Suatu variabel ber-type set dapat menyimpan kosong, satu, dua, tiga, atau lebih nilai dari suatu range. Dia bahkan dapat menyertakan semua nilai. Berikut ini adalah contoh dari suatu set:
type Letters = set of Uppercase;
Sekarang saya dapat mendefinisikan suatu variabel dengan type ini dan memberikan nilai kedalam variabel itu dengan nilai dari original type. Untuk mengindikasikan suatu nilai dari sebuah set, Anda menuliskan daftar yang dipisahkan dengan tanda koma, diapit dengan tanda kurung siku. Code berikut menunjukkan cara pemberian nilai kedalam suatu variabel dengan bebeberapa nilai, satu nilai, dan kosong:
var Letters1, Letters2, Letters3: Letters; begin Letters1 := ['A', 'B', 'C']; Letters2 := ['K']; Letters3 := [];
Dalam Delphi, suatu set biasanya digunakan untuk mengindikasikan tanda (flag) yang non-ekslusif. Contohnya, dua baris code berikut ini (yang merupakan bagian dari library Delphi) mendeklarasikan suatu enumeration dari icon yang dimungkinkan untuk suatu border window dan set type yang sesuai:
type TBorderIcon = (biSystemMenu, biMinimize, biMaximize, biHelp); TBorderIcons = set of TBorderIcon;
Sebenarnya, suatu window mungkin tidak mempunyai icon apapun, hanya satu, atau lebih dari satu. Ketika bekerja dengan Object Inspector (lihat Gambar 4.3), Anda dapat memberikan nilai suatu set dengan memperluas pemilihan (expanding the selection) dengan melakukan double-click pada nama property atau click pada tanda plus disebelah kiri dan mengaktifkan atau menonaktifkan tiap nilai.
Gambar 4.3: Suatu property ber-type set dalam Object Inspector
Property lain yang berdasarkan set type adalah font style. Nilai yang dimungkinkan mengindikasikan font yang bold, italic, underline, dan strikethrough. Tentunya font yang sama dapat mempunyai atribut italic dan bold, tanpa atribut, atau semua atribut. Untuk alasan ini, property ini dideklarasikan sebagai set. Anda dapat memberikan nilai pada set ini dalam suatu contoh program sebagai berikut:
Font.Style := []; // no style Font.Style := [fsBold]; // bold style only Font.Style := [fsBold, fsItalic]; // two styles
Anda juga dapat memanipulasi set dalam banyak cara, termasuk menambahkan dua variabel dengan type set yang sama (atau, untuk lebih tepatnya, menghitung penggabungan [union] dari kedua variabel set tadi):
Font.Style := OldStyle + [fsUnderline]; // two sets
Lagi, Anda dapat menggunakan contoh OrdType yang disertakan dalam directory TOOLS dari source code buku ini untuk melihat daftar nilai yang dimungkinkan untuk sekian banyak set yang didefinisikan oleh Delphi component library.
Array types mendefinisikan daftar dari sejumlah elemen dengan type tertentu. Anda biasanya dapat menggunakan index dalam tanda kurung siku untuk mengakses salah satu elemen array. Tanda kurung siku ini juga dipakai untuk menentukan nilai yang dimungkinkan untuk index saat mendefinisikan array. Misalnya, Anda dapat menentukan suatu kelompok yang terdiri dari 24 integer dengan code seperti berikut:
type DayTemperatures = array [1..24] of Integer;
Dalam definisi array, Anda harus melewatkan suatu subrange type dalam tanda kurung siku, atau mendefinisikan suatu subrange type yang baru menggunakan dua konstanta dengan ordinal type. Subrange ini akan menentukan index array yang valid. Karena Anda menentukan nilai index atas dan bawah dari array, maka index tidaklah perlu untuk didasarkan dari nilai 0, seperti yang disyaratkan dalam bahasa C, C++, Java, maupun bahasa pemrograman lainnya.
Karena index array didasarkan pada subrange, Delphi dapat memeriksa range mereka seperti yang telah kita lihat sebelumnya. Suatu konstanta subrange yang tidak valid akan menghasilkan error saat kompilasi; dan bila kita menggunakan suatu index yang melebihi range yang telah ditentukan sebelumnya, maka akan muncul pesan kesalahan saat aplikasi dijalankan jika pilihan kompilasi yang terkait diaktifkan.
Menggunakan definisi array diatas, Anda dapat mengisi nilai suatu variabel DayTempl dengan type DayTemperaturs seperti berikut ini:
type DayTemperatures = array [1..24] of Integer; var DayTemp1: DayTemperatures; procedure AssignTemp; begin DayTemp1 [1] := 54; DayTemp1 [2] := 52; ... DayTemp1 [24] := 66; DayTemp1 [25] := 67; // compile-time error
Suatu array dapat mempunyai lebih dari satu dimensi, seperti pada contoh berikut:
type MonthTemps = array [1..24, 1..31] of Integer; YearTemps = array [1..24, 1..31, Jan..Dec] of Integer;
Kedua type array ini dibangun dengan type dasar yang sama. Jadi Anda dapat mendeklarasikannya menggunakan type data sebelumnya, seperti pada contoh berikut:
type MonthTemps = array [1..31] of DayTemperatures; YearTemps = array [Jan..Dec] of MonthTemps;
Deklarasi ini mengubah urutan index seperti pada contoh diatas, tapi juga memungkinkan pengisian variabel secara satu blok utuh. Misalnya, perintah berikut akan menyalin temperatur January ke February:
var ThisYear: YearTemps; begin ... ThisYear[Feb] := ThisYear[Jan];
Anda juga dapat mendefinisikan zero-based array, suatu type array dengan batas bawah yang diatur ke nol. Secara umum, penggunaan batasan yang logis lebih menguntungkan, karena kita tidak perlu menggunakan index 2 untuk mengakses item ke tiga, dan seterusnya. Walau demikian, Windows menggunakan sekian banyak zero-based array (karena didasarkan pada bahasa C) dan library komponen Delphi pun cenderung demikian.
Jika Anda perlu bekerja pada array, Anda dapat selalu memeriksa batasannya dengan menggunakan fungsi standar Low dan High, yang mengembalikan nilai batas bawah dan atas. Menggunakan Low dan High ketika mengoperasikan array sangatlah disarankan, khususnya dalam loop (pengulangan), karena membuat code kita tidak bergantung pada range array yang digunakan. Selanjutnya, Anda dapat mengganti range yang telah dideklarasikan, dan code yang menggunakan Low dan High tetap bisa bekerja. Jika Anda menuliskan code yang menggunakan nilai range array secara statis, Anda harus memperbaharui code tersebut bilamana ukuran array berubah. Low dan High membuat code Anda mudah dipelihara dan lebih stabil.
Note: Secara kebetulan, tidak ada penurunan kinerja saat aplikasi berjalan bila kita menggunakan Low dan High untuk array. Mereka diubah saat kompilasi menjadi ekspresi konstanta, bukan pemanggilan fungsi sebenarnya. Pengubahan saat kompilasi untuk ekspresi dan pemanggilan fungsi ini juga terjadi untuk sekian banyak fungsi system sederhana yang lainnya.
Delphi menggunakan array terutama pada array property. Kita telah melihat suatu contoh dari property ini dalam contoh TimeNow, untuk mengakses property Items dari komponen ListBox. Saya akan menunjukkan beberapa contoh array property dalam bab selanjutnya, saat mendiskusikan loop dalam Delphi.
Note: Delphi 4 memperkenalkan konsep dynamic array dalam Object Pascal, yaitu array yang dapat diubah ukurannya saat aplikasi berjalan dengan mengalokasikan memory secukupnya. Menggunakan dynamic array cukup mudah, tapi dalam pembahasan Pascal ini saya pikir bukan topic yang seharusnya kita bahas sekarang. Anda dapat menemukan penjelasan mengenai dynamic array Delphi dalam Bab 8.
Record type mendefinisikan suatu kumpulan tetap dari item dengan type yang berbeda. Setiap elemen, atau field mempunyai type-nya sendiri. Definisi dari record type memunculkan daftar semua field, memberikan masing-masing sebuah nama yang bisa Anda gunakan untuk mengaksesnya kemudian.
Berikut ini adalah daftar singkat mengenai definisi suatu record type, deklarasi variabel dengan type itu, dan beberapa perintah yang menggunakan variabel ini:
type Date = record Year: Integer; Month: Byte; Day: Byte; end; var BirthDay: Date; begin BirthDay.Year := 1997; BirthDay.Month := 2; BirthDay.Day := 14;
Class dan object dapat dianggap sebagai perluasan dari record type. Library Delphi cenderung menggunakan class type ketimbang record type, tapi ada beberapa record type yang didefinisikan oleh Windows API.
Record type dapat mempunyai bagian berupa variant; yaitu beberapa field dapat dipetakan ke daerah memory yang sama, walaupun mereka mempunyai data type yang berbeda. (Hal ini bersesuaian dengan union dalam bahasa C). Secara alternatif, Anda dapat menggunakan field ataupun kelompok field variant untuk mengakses lokasi memory yang sama dalam suatu record, tapi mempertimbangkan nilai tersebut dari perspektif yang berbeda. Tujuan utama penggunaan type ini adalah untuk menyimpan data berbeda yang mirip dan memperoleh hasil yang mirip dengan typecasting (saat ini typecasting juga telah diperkenalkan di Pascal). Penggunaan variant record type sebagian besar telah digantikan dengan teknik object-oriented dan teknik modern lainnya, meskipun Delphi menggunakan mereka dalam kasus-kasus aneh tertentu.
Penggunaan variant record type tidaklah aman dan tidak merupakan suatu praktek pemrograman yang disarankan, khususnya bagi para pemula. Programmer ahli dapat menggunakan variant record type, dan inti library Delphi juga menggunakannya. Anda mungkin tidak perlu berurusan dengan itu sampai Anda menjadi ahli dalam Delphi.
Suatu pointer type mendefinisikan suatu variabel yang menyimpan alamat memory dari variabel lain dengan suatu data type tertentu (atau dengan type yang belum terdefinisi). Jadi suatu variabel pointer secara tidak langsung merujuk pada suatu nilai. Definisi pointer type tidaklah didasarkan pada suatu keyword tertentu, tapi menggunakan karakter khusus. Simbol khusus ini adalah tanda caping / caret (^):
type PointerToInt = ^Integer;
Setelah Anda mendefinisikan suatu variabel pointer, Anda dapat mengisikannya dengan alamat variabel lainnya dengan type yang sama, menggunakan operator @:
var P: ^Integer; X: Integer; begin P := @X; // change the value in two different ways X := 10; P^ := 20;
Ketika Anda mempunyai suatu pointer P, dengan ekspresi P Anda merujuk pada alamat yang ditunjuk oleh pointer tersebut, dan dengan ekspresi P^ Anda merujuk pada isi sebenarnya dari lokasi memory tadi. Untuk alasan inilah, dalam potongan code diatas ^P merujuk ke X.
Ketimbang merujuk pada lokasi memory yang sudah ada, suatu pointer juga dapat merujuk pada suatu memory block baru yang dialokasikan secara dinamis (pada heap memory area) dengan prosedur New. Dalam kasus ini, ketika Anda tidak membutuhkan pointer ini lagi, Anda harus membebaskan memory yang telah Anda alokasikan secara dinamis, dengan memanggil prosedur Dispose.
var P: ^Integer; begin // initialization New (P); // operations P^ := 20; ShowMessage (IntToStr (P^)); // termination Dispose (P); end;
Jika suatu pointer tidak mempunyai nilai, Anda dapat mengisinya dengan nil. Lalu Anda dapat mencocokkan apakah pointer tersebut nil untuk memeriksa apakah pointer tersebut merujuk ke suatu nilai. Ini sering digunakan, karena merujuk dengan pointer yang invalid akan menyebabkan access violation (ini juga dikenal dengan suatu general protection fault, GPF):
procedure TFormGPF.BtnGpfClick(Sender: TObject); var P: ^Integer; begin P := nil; ShowMessage (IntToStr (P^)); end;Anda dapat melihat contoh dari hasil code ini dengan menjalankan contoh GPF (atau melihat gambar 4.4). Contoh ini mengandung potongan code diatas.
Gambar 4.4: System error karena mengakses nil pointer, dari contoh GPF.
Dalam program yang sama, Anda dapat melihat contoh mengakses data secara aman. Dalam contoh kedua ini, pointer diisi dengan local variabel yang suda hada, dan dengan aman bisa digunakan, tapi bagaimanapun juga saya telah menambahkan suatu pemeriksaan keamanan:
procedure TFormGPF.BtnSafeClick(Sender: TObject); var P: ^Integer; X: Integer; begin P := @X; X := 100; if P <> nil then ShowMessage (IntToStr (P^)); end;
Delphi juga mendefinisikan suatu Pointer data type, yang mengindikasikan untyped pointer (seperti void* dalam bahasa C). Jika Anda menggunakan untyped pointer, Anda sebaiknya menggunakan GetMem ketimbang New. Prosedur GetMem diperlukan tiap kali ukuran variabel memory yang akan dialokasikan belum didefinisikan.
Kenyataan bahwa pointer jarang diperlukan dalam Delphi adalah suatu keuntungan yang menarik dari lingkungan ini. Bagaimanapun juga, mengerti cara kerja pointer adalah penting bagi pemrograman tingkat lanjut dan untuk pemahaman penuh dari object model dalam Delphi, yang menggunakan pointer "dibalik layar".
Note: Although you don’t use pointers often in Delphi, you do frequently use a very similar construct—namely, references. Every object instance is really an implicit pointer or reference to its actual data. However, this is completely transparent to the programmer, who uses object variables just like any other data type.
Suatu type constructor khusus dalam Pascal adalah file type. File type mewakili suatu file di disk fisik, tentunya merupakan hal yang khusus dalam bahasa Pascal. Anda dapat mendefinisikan sebuah data type file yang baru seperti berikut:
type IntFile = file of Integer;
Lalu Anda dapat membuka file fisik yang diasosiasikan dengan struktur ini dan menuliskan suatu nilai integer kedalamnya atau membaca nilai sekarang dari file tersebut.
Catatan Penulis: Contoh untuk operasi file ini merupakan bagian lama dari buku Mastering Delphi dan saya berencana untuk menambahkannya kemari)
Penggunaan file dalam Pascal cukup mudah, namun dalam Delphi telah menyediakan beberapa komponen yang mampu menyimpan atau memuat isi ke maupun dari suatu file. Juga ada dukungan serialisasi, dalam bentuk stream, dan juga ada dukungan untuk database.
Bab ini membahas user-defined data type melengkapi liputan kita tentang Pascal type system. Sekarang kita telah siap untuk melihat statement yang disediakan oleh bahasa Pascal untuk mengoperasikan variabel yang telah kita definisikan sebelumnya.
© Copyright Marco Cantù, Wintech Italia Srl 1995-2000
© Copyright of the Indonesian Translation by Hianoto Santoso, 2002