Friday, December 23, 2016

Bab 9. C++ Untuk Programer



Bab. 9 Pemrosesan File


Tujuan Instruksional
      ·         File dan aliran.
      ·         Menciptakan file sekuensial.
      ·         Membaca data dari file sekuensial.
      ·         File akses-acak.
       ·         Menciptkan file akses-acak.
·         Menulis secara acak ke dalam file akses-acak.
·         Membaca file akses-acak secara sekuensial.







9.1 Introduksi

Penyimpanan data di dalam memori bersifat sementara. File dipakai untuk menyimpan data secara permanen. Komputer menyimpan file pada divais penyimpanan sekunder, seperti hard disk, CD, DVD, flash disk, dan lainnya. Pada bab ini, akan dijelaskan bagaimana membangun program C++ yang menciptakan, memperbarui, dan memproses data file. Akan dipelajari tentang file sekuensial dan fila akses-acak. Akan dibandingkan pemrosesan file data-terformat dengan pemrosesan file data-mentah.

9.2 File dan Aliran
C++ memandang bahwa setiap file hanyalah sebuah runtun byte (Gambar 9.1). Setiap file diakhiri dengan penanda end-of-file atau sejumlah byte tertentu yang direkam di dalam suatu struktur data sistem operasi. Ketika sebuah file dibuka, suatu objek diciptakan, dan aliran diasosiasikan dengan objek tersebut. Anda telah melihat bahwa objek cin, cout, cerr, dan clog diciptakan ketika <iostream> dicantumkan. Aliran yang diasosiasikan dengan objek menyediakan kanal komunikasi antara program dan file atau divais tertentu. Sebagai contoh, objek cin (objek aliran masukan standard) memampukan program untuk memasukkan data dari papanketik atau dari divais lainnya, objek cout (objek aliran keluaran standard) memampukan program untuk mengeluarkan data ke layar atau divais lainnya, dan objek cerr dan clog (objek aliran error standard) memampukan program untuk mengeluarkan error ke layar atau ke divais lainnya.

Gambar 9.1 C++ memandang file sebagai sebuah runtun byte


Untuk melakukan pemrosesan file di dalam C++, header <iostream> dan <fstream> harus dicantumkan. Header <fstream> menyertakan definisi untuk template kelas aliran basic_ifstream (untuk masukan file), basic_ofstream (untuk keluaran file), dan basic_fstream (untuk keluaran dan masukan file). Setiap template kelas memiliki spesialisasi template terdefinisi yang memampukan I/O char.

Selain itu, pustaka <fstream> menyediakan sejumlah nama alias typedef untuk spesialisasi template ini. Sebagai contoh, typedef ifstream merepresentasikan sebuah spesialisasi dari basic_ifstream yang memampukan masukan char dari sebuah file. Sama halnya, typedef ofstream merepresentasikan spesialisasi atas basic_ofstream yang memampukan keluaran char ke file. Begitu juga dengan typedef fstream merepresentasikan spesialisasi atas basic_fstream yang memampukan masukan char dari dan keluaran char ke file.

File dibuka dengan cara menciptakan objek dari spesialisasi template aliran tersebut. Template tersebut diderivasi dari template kelas basic_istream, basic_ostream, dan basic_iostream. Jadi, semua fungsi anggota, operator, dan manipulator yang dimiliki oleh template tersebut dapat juga diterapkan pada aliran file. Gambar 9.2 menyimpulkan relasi pewarisan atas kelas I/O yang telah didiskusikan sejauh ini.

Gambar 9.2 Sebagian dari hirarki pewarisan template kelas I/O



16.3 Menciptakan File Sekuensial
C++ tidak memakai struktur apapun pada sebuah file. Jadi, Anda harus membuat file menjadi terstruktur sesuai dengan kebutuhan aplikasi Anda. Contoh berikut menunjukkan bagaimana Anda dapat melakukan  penstrukturan sederhana pada suatu file.

Gambar 16.3 menciptakan sebuah file sekuensial yang bisa dipakai untuk sistem akun sederhana di dalam suatu bank. Untuk setiap klien, program mendapatkan nomor akun, nama, dan saldo klien. Data yang didapatkan untuk tiap klien membentuk rekaman bagi klien tersebut. Nomor akun berperan sebagai kunci rekaman; yaitu, program menciptakan rekaman file dengan urutan nomor akun. Program ini mengasumsikan bahwa pengguna memasukkan rekaman sesuai dengan urutan nomor akun. Dalam sistem akun yang lebih komprehensif, kapabilitas pengurutan tentu diperlukan.

Gambar 9.1 Menggunakan Spesialisasi Template Fungsi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Gambar 9.3: gambar9_03.cpp
// Menciptakan suatu file sekuensial.
#include <iostream>
#include <string>
#include <fstream> // aliran file
#include <cstdlib>
using namespace std;

int main()
{
  // konstruktor ofstream membuka file
  ofstream keluarFileKlien( "klien.txt", ios::out );

  // keluar program jika tidak bisa menciptakan file
  if( !keluarFileKlien )// operator ! dioverload
  {
    cerr << "File tidak bisa dibuka" << endl;
    exit( 1 );
  } // akhir dari if

  cout << "Masukkan akun, nama, dan saldo." << endl
    << "Masukkan end-of-file untuk mengakhiri masukan.\n? ";

  int akun;
  string nama;
  double saldo;

  // membaca akun, nama, dan saldo dari cin kemudian menempatkannya di dalam file
  while( cin >> akun >> nama >> saldo )
  {
    keluarFileKlien << akun << ' ' << nama << ' ' << saldo << endl;
    cout << "? ";
  } // akhir dari while
} // akhir dari main

Masukkan akun, nama, dan saldo.
Masukkan end-of-file untuk mengakhiri masukan.
? 100 Rico 32.90
? 200 Robert 342.37
? 300 Ika 0.00
? 400 Rotua -43.32
? 500 Teti 323.45
?^Z

Seperti dinyatakan sebelumnya, file dibuka dengan menciptakan objek ifstream, ofstream, dan fstream. Pada Gambar 9.3, file dibuka untuk keluaran, sehingga objek ofstream yang diciptakan. Dua argumen dilewatkan kepada konstruktor objek, nama file dan mode file-open (baris 12). Untuk suatu objek ofstream, mode file-open dapat berupa ios::out untuk mengeluarkan data ke sebuah file atau berupa ios::app untuk menyambungkan data ke akhir suatu file (tanpa memodifikasi data yang sebelumnya sudah ada di dalam file itu). Data di dalam file yang sudah ada bila dibuka dengan mode ios::out akan dihapus. Jika file yang dispesifikasi belum ada, maka objek ofstream akan menciptakan file tersebut dengan nama yang diberikan.

Baris 12 mencipatakan sebuah objek ofstream bernama keluarFileKlien yang diasosiasikan dengan file klien.txt, yang dibukan untuk keluaran. Argumen “klien.txt” dan ios::out dilewatkan kepada konstruktor ofstream, yang membukan file untuk menyediakan kanal komunikasi dengan file itu. Secara default, objek ofstream dibuka untuk keluaran, jadi baris 12 dapat menggunakan statemen alternatif

ofstream keluarFileKlien( "clients.txt" );

untuk membuka file klien.txt untuk keluaran. Gambar 9.4 membuat daftar beberapa mode file-open. Semua mode itu dapat dimodifikasikan, seperti yang akan didiskusikan nanti.

Gambar 9.4 Beberapa Mode Pembukaan File

Mode
Penjelasan
ios::app

ios::ate


ios::in

ios::out

ios::trunc

ios::binary
Menyambung keluaran ke akhir file.

Membuka file untuk keluaran dan bergerak ke akhir file (biasanya untuk menyambung data ke dalam sebuah file). Data dapat ditulis di sembarang tempat di dalam file.

Membuka file untuk masukan.

Membuka file untuk keluaran.

Membuang isi file (ini juga aksi default untuk ios::out).

Membuka file untuk masukan dan keluaran biner (non-teks).

Sebuah objek ofstream dapat diciptakan tanpa membuka file tertentu, karena file dapat diasosiasikan ke objek itu belakangan. Sebagai contoh, statemen

ofstream keluarFileKlien;

menciptakan sebuah objek ofstream bernama keluarFileKlien. Fungsi anggota open dari kelas ofstream membuka file dan mengasosiasikannya ke objek ofstream yang sudah ada sebagai berikut:

keluarFileKlien.open( "clients.txt", ios::out );

Setelah menciptakan sebuah objek ofstream dan mencoba membukanya, program kemudian menguji apakah operasi pembukaan berhasil atau tidak. Statemen if pada baris 15-19 menggunakan fungsi anggota operator ! teroverload untuk menentukan apakah operasi pembukaan berhasil atau tidak. Kondisi ini menghasilkan nilai true jika salah satu failbit atau badbit ditetapkan true untuk aliran pada operasi open. Beberapa kejadian error di antaranya adalah mencoba membuka file yang tidak ada untuk pembacaan, mencoba membuka file atau menulis file dari sebuah direktori dimana Anda tidak memiliki ijin akses,dan membuka file untuk menulis ketika memori tidak lagi tersedia.

Jika kondisi mengindikasikan terjadinya percobaan yang tidak berhasil dalam membuka sebuah file, maka baris 17 menampilkan pesan error “File tidak bisa dibuka”, dan baris 18 memanggil fungsi exit untuk menghentikan program. Argumen 0 pada fungsi exit mengindikasikan bahwa program berhenti secara normal; Argumen bernilai selain 0 mengindikasikan bahwa program berhenti karena terjadinya suatu error.

Fungsi anggota ios lainnya, operator void *, mengkonversi aliran menjadi sebuah pointer, sehingga dapat diuji sebagai 0 (pointer null) atau tak-null (sembarang nilai pointer). Ketika sebuah nilai pointer digunakan sebagai kondisi, C++ menginterpretasikan pointer null sebagai nilai false dan menginterpretasikan pointer tak-null sebagai true. Jika failbit atau badbit ditetapkan (true) untuk aliran, maka dihasilkan nilai balik 0 (false). Kondisi di dalam statemen while pada baris 29-33 memanggil fungsi anggota operator void * pada cin secara implisit. Kondisi tetap bernilai true sepanjang tidak ada failbit atau badbit yang bernilai true untuk cin.

Jika baris 12 membuka file secara sukses, program akan mulai memproses data. Baris 21-22 mendesak pengguna untuk memasukkan nilai untuk tiga bidang pada tiap rekaman atau memasukkan indikator end-of-file tidak pengentrian data selesai dilakukan. Gambar 9.5 mencantumkan kombinasi kunci untuk indikator end-of-file pada berbagai sistem operasi.

Gambar 9.5 Kombinasi Kunci untuk Indikator End-of-File

Sistem Operasi
Kombinasi Kunci
UNIX/LINUX/Mac OS X

Microsoft Windows
<Ctrl-d>

<Ctrl-z> ( kadang-kadang diikuti dengan penekanan ENTER)

Baris 29 mengekstrak setiap himpunan data dan menentukan apakah end-of-file telah dimasukkan oleh pengguna atau tidak. Ketika end-of-file dijumpai atau bila data buruk dimasukkan, operator void * menghasilkan pointer null (yang dikonversi menjadi nilai false) dan statemen while berhenti. Pengguna mengentrikan end-of-file untuk menginformasikan program bahwa pengentrian data telah selesai. Indikator end-of-file ditetapkan ketika pengguna menekan kombinasi kunci tertentu, yang dicantumkan pada Gambar 9.5. Statemen while tetap beriterasi sampai indikator end-of-file dientrikan oleh pengguna.

Baris 31 menuliskan sehimpunan data ke dalam file klien.txt, menggunakan operator penyisipan aliran << dan objek keluarFileKlien yang diasosiasikan denagan file tersebut di awal program. Data yang diciptakan pada Gambar 16.3 berupa file teks sederhana, jadi dapat dibaca oleh sembarang text editor.

Begitu pengguna mengentrikan indikator end-of-file, maka fungsi main berhenti. Ini secara implisit memanggil destruktor keluarFileKlien, yang menutup file klien.txt. Anda dapat pula menutup objek ofstream secara eksplisit, menggunakan fungsi anggota close di dalam statemen

keluarFileKlien.close();

Pada contoh eksekusi program, pengguna mengentrikan informasi untuk lima akun, kemudian memasukkan indikator end-of-file untuk mengindikasikan bahwa pengentrian data telah selesai. Untuk memverifikasi bahwa program telah menciptakan file dengan sukses, pada bagian berikutnya akan ditunjukkan bagaimana menciptakan sebuah program untuk membaca file ini dan menampilkan isinya.

9.3 Membaca Data dari File Sekuensial
File menyimpan data agar dapat dibaca dan diproses ketika dibutuhkan. Pada bagian sebelumnya telah didemonstrasikan bagaimana menciptakan sebuah file sekuensial. Sekarang akan didiskusikan bagaimana membaca data secara sekuensial dari suatu file. Gambar 9.6 membaca dan menampilkan lima rekaman dari file klien.txt, yang telah diciptakan menggunakan program pada Gambar 9.3. Penciptaan sebuah objek ifstream dimaksudkan untuk membuka file untuk masukan. Konstruktor ifstream dapat menerima nama file dan mode file-open sebagai argumen. Baris 15 menciptakan suatu objek ifstream yang dinamakan masukanFileKlien dan mengasosiasikannya dengan file klien.txt. Argumen di dalam kurung dilewatkan kepada konstruktor ifstream, yang membuka file dan menyediakan kanal komunikasi dengan file tersebut.

Gambar 9.6 Membaca dan Menampilkan Isi Suatu File Sekuensial

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Gambar 9.6: gambar9_06.cpp
// Membaca dan menampilkan isi suatu file sekuensial.
#include <iostream>
#include <fstream> // aliran file
#include <iomanip>
#include <string>
#include <cstdlib>
using namespace std;

void keluaranBaris( int, const string, double ); // prototipe

int main()
{
  // konstruktor ifstream untuk membuka file
  ifstream masukanFileKlien( "klien.txt", ios::in );

  // keluar program jika ifstream tidak bisa membukan file
  if( !masukanFileKlien )
  {
    cerr << "File tidak bisa dibuka" << endl;
    exit( 1 );
  } // akhir dari if

  int akun;
  string nama;
  double saldo;

  cout << left << setw( 10 ) << "Akun" << setw( 13 )
    << "Nama" << "Saldo" << endl << fixed << showpoint;

  // menampilkan setiap rekaman di dalam file
  masukanFileKlien >> akun >> nama >> saldo
    keluaranBaris( akun, nama, saldo );
} // akhir dari main

// menampilkan satu rekaman dari file
void keluaranBaris( int akun, const string nama, double saldo )
{
  cout << left << setw( 10 ) << akun << setw( 13 ) << nama
    << setw( 7 ) << setprecision( 2 ) << right << saldo << endl;
} // akhir dari fungsi keluaranBaris

Akun            Nama            Saldo
100             Rico            32.90
200             Robert          342.37
300             Ika               0.00
400             Rotua           -43.32
500             Teti            323.45

Objek dari kelas ifstream dibuka untuk masukan secara default, sehingga statemen

ifstream masukanFileKlien( "klien.txt" );

membuka klien.txt untuk masukan. Sama seperti objek ofstream, objek ifstream dapat diciptakan tanpa perlu membuka fil tertentu, karena sebuah file dapat diasosiasikan dengannya belakangan.

Sebelum mencoba membaca data dari file, program menggunakan kondisi !masukanFileKlien untuk menentukan apakah file dibuka secara sukes atau tidak. Baris 32 membaca sehimpunan data (rekaman) dari file. Setelah baris 32 dieksekusi pertama kali, akun memiliki nilai 100, nama memiliki nilai “Rico”, dan saldo memiliki nilai 32.90. Setiap kali baris 32 dieksekusi, ia membaca rekaman lain dari file dan ditugaskan kepada variabel akun, nama, dan saldo. Baris 33 menampilkan rekaman, menggunakan fungsi keluaranBaris (baris 37-41), yang menggunakan manipulator aliran terparameterisasi untuk memformat data tampilan. Ketika end-of-file dijumpai, pemanggilan implisit terhadap operator void * di dalam kondisi while menghasilkan nilai balik pointer null (yang dikonversi menjadi false), destruktor ifstream menutup file, dan program berhenti.

Untuk membaca atau mengambil data secara sekuensial dari sebuah file, program nirmalnya mulai membaca dari awal file dan membaca semua data secara berurutan sampai semua data yang diinginkan terbaca. Beberapa fungsi anggota dari kelas istream dan ostream disediakan untuk memposisikan-ulang pointer di dalam file. Fungsi anggota itu adalah seekg untuk istream dan seekp untuk ostream. Setiap objek istream memiliki sebuah pointer get, yang mengindikasikan jumlah byte di dalam file dimana lokasi masukan berikutnya dilakukan, dan setiap objek ostream memiliki pointer put, yang mengindikasikan jumlah byte di dalam file diman lokasi keluaran berikutnya ditempatkan. Statemen

masukanFileKlien.seekg( 0 );

memposisikan-ulang pointer di dalam file untuk menunjuk ke awal file (lokasi 0) yang diasosiasikan dengan masukanFileKlien. Argumen dari fungsi seekg adalah sebuah integer long. Argumen keduanya dapat dispesifikasi untuk mengindikasikan arah pembacaan, yang dapat berupa ios::beg (default) untuk memposisikan pointer ke awal aliran, ios::cur untuk memposisikan pointer ke posisi sekarang di dalam aliran, atau ios::end untuk memposisikan pointer ke akhir sebuah aliran. Pointer penunjuk di dalam file merupakan sebuah nilai integer yang menspesifikasi lokasi di dalam file sebagai jumlah byte dari lokasi awal file. Beberapa contoh pemosisian pointer get adalah

// memposisikan ke byte ke-n dari objekFile (diasumsikan ios::beg)
objekFile.seekg( n );

// memposisikan n byte maju di dalam objekFile
objekFile.seekg( n, ios::cur );

// memposisikan n byte mundur dari akhir objekFile
objekFile.seekg( n, ios::end );

// memposisikan ke akhir objekFile
objekFile.seekg( 0, ios::end );

Operasi yang sama dapat dilakukan menggunakan fungsi anggota seekp dari kelas ostream. Fungsi anggota tellg dan tellp disediakan untuk menghasilkan nilai balik berupa lokasi sekarang dari pointer put dan get. Statemen berikut menugaskan nilai pointer get kepada variabel lokasi bertipe long:

lokasi = objekFile.tellg();

Gambar 9.7 memampukan seorang manajer kredit untuk menampilkan informasi akun untuk para kustomer dengan saldo nol (kustomer yang tidak meminjam uang berapapun kepada bank), saldo kredit (negatif) (kustomer yang meminjamkan uang kepada bank), dan saldo debit (positif) (kustomer yang meminjam uang dari bank). Program menampilkan sebuah menu dan mengijinkan manajer kredit untuk memasukkan salah satu dari tiga opsi untuk mendapatkan informasi kredit. Opsi 1 menghasilkan daftar akun dengan saldo nol. Opsi 2 menghasilkan daftar akun dengan saldo kredit. Opsi 3 menghasilkan daftar akun dengan saldo debit. Opsi 4 menghentikan eksekusi program. Pengentrian opsi yang tidak valid akan menampilkan desakan agar sang manajer untuk memasukkan pilihan lagi. Baris 65-66 memampukan program untuk membaca dari awal file setelah penanda EOF dibaca.




Gambar 9.7 Program untuk Menampilkan Akun

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// Gambar 9.7: gambar9_07.cpp
// Program untuk menampilkan akun.
#include <iostream>
#include <fstream>
#include <iomanip>
#include <string>
#include <cstdlib>
using namespace std;

enum TipePermintaan { SALDO_NOL = 1, SALDO_KREDIT, SALDO_DEBIT, AKHIR };
int getPermintaan();
bool harusTampil( int, double );
void keluaranBaris( int, const string, double );

int main()
{
  // konstruktor ifstream untuk membuka file
  ifstream masukanFileKlien( "klien.txt", ios::in );

  // keluar program jika ifstream tidak bisa membuka file
  if( !masukanFileKlien )
  {
    cerr << "File tidak bisa dibuka" << endl;
    exit( 1 );
  } // akhir dari if

  int permintaan;
  int akun;
  string nama;
  double saldo;

  // mendapatkan permintaan pengguna (saldo nol, kredit, atau debit)
  permintaan = getPermintaan();

  // memproses permintaan pengguna
  while ( permintaan != AKHIR )
  {
    switch ( permintaan )
    {
      case SALDO_NOL:
        cout << "\nAkun dengan saldo nol:\n";
        break;
      case SALDO_KREDIT:
        cout << "\nAkun dengan saldo kredit:\n";
        break;
      case SALDO_DEBIT:
        cout << "\nAkun dengan saldo debit:\n";
        break;
    } // akhir dari switch

    // membaca akun, nama, dan saldo dari file
    masukanFileKlien >> akun >> nama >> saldo;

    // menampilkan isi file (sampai eof)
    while( !masukanFileKlien.eof() )
    {
      // menampilkan rekaman
      if ( harusTampil( permintaan, saldo ) )
        keluaranBaris( akun, nama, saldo );

      // membaca akun, nama, dan saldo dari file
      masukanFileKlien >> akun >> nama >> saldo;
    } // akhir dari while sebelah dalam

    masukanFileKlien.clear(); // mereset eof untuk masukan berikutnya
    masukanFileKlien.seekg( 0 ); // mereposisi ke awal file
    permintaan = getPermintaan(); // mendapatkan permintaan tambahan dari pengguna
  } // akhir dari while sebelah luar

  cout << "Akhir dari eksekusi." << endl;
} // akhir dari main

// mendapatkan permintaan dari pengguna
int getPermintaan()
{
  int permintaan; // permintaan dari pengguna

  // menampilkan pilihan permintaan
  cout << "\nMasukkan pilihan" << endl
    << " 1 - Daftar akun dengan saldo nol" << endl
    << " 2 - Daftar akun dengan saldo kredit" << endl
    << " 3 - Daftar akun dengan saldo debit" << endl
    << " 4 - Akhir dari eksekusi" << fixed << showpoint;

  do // membaca permintaan pengguna
  {
    cout << "\n? ";
    cin >> permintaan;
  } while ( permintaan < SALDO_NOL && permintaan > AKHIR );

  return permintaan;
} // akhir dari fungsi getPermintaan

// menentukan apakah menampilkan rekaman yang diberikan
bool harusTampil( int tipe, double saldo )
{
  // menentukan apakah harus menampilkan saldo nol
  if ( tipe == SALDO_NOL && saldo == 0 )
    return true;

  // menentukan apakah harus menampilkan saldo kredit
  if ( tipe == SALDO_KREDIT && saldo < 0 )
    return true;

  // menentukan apakah harus menampilkan saldo debit
  if ( tipe == SALDO_DEBIT && saldo > 0 )
    return true;

  return false;
} // akhir dari fungsi harusTampil

// menampilkan satu rekaman dari file
void keluaranBaris( int akun, const string nama, double saldo )
{
  cout << left << setw( 10 ) << akun << setw( 13 ) << nama
    << setw( 7 ) << setprecision( 2 ) << right << saldo << endl;
} // akhir dari fungsi keluaranBaris

Masukkan pilihan
1 - Daftar akun dengan saldo nol
2 - Daftar akun dengan saldo kredit
3 - Daftar akun dengan saldo debit
4 - Akhir dari eksekusi
? 1

Akun dengan saldo nol:
300             Ika             0.00

Masukkan pilihan
1 - Daftar akun dengan saldo nol
2 - Daftar akun dengan saldo kredit
3 - Daftar akun dengan saldo debit
4 - Akhir dari eksekusi
? 2

Akun dengan saldo kredit:
400             Rotua           -43.32

Masukkan pilihan
1 - Daftar akun dengan saldo nol
2 - Daftar akun dengan saldo kredit
3 - Daftar akun dengan saldo debit
4 - Akhir dari eksekusi
? 3

Akun dengan saldo debit:
100             Rico              32.90
200             Robert          342.37
500             Teti            323.45

Masukkan pilihan
1 - Daftar akun dengan saldo nol
2 - Daftar akun dengan saldo kredit
3 - Daftar akun dengan saldo debit
4 - Akhir dari eksekusi
? 4
Akhir dari eksekusi.

9.4 File Akses-Acak
Sejauh ini, Anda telah melihat bagaimana menciptakan file sekuensial. File sekuensial cocok untuk aplikasi akses-instan, dimana di dalamnya rekaman tertentu harus dicari lokasinya secara sangat cepat. Aplikasi akes-instan yang umum dipakai adalah sistem reservasi penerbangan, sistem perbankan, dan sistem pemrosesan transaksi lain yang memerlukan akses cepat terhadap data tertentu. Bank bisa saja memiliki ratusan atau bahkan jutaan nasabah, yang sangat memerlukan sistem akes instan di dalam pelayanannya. Akses instan ini diwujudkan dengan file akes-acak. Rekaman individual atas sebuah file akses-acak dapat diakses secara langsung (dan secara cepat) tanpa harus mencari atau meneliti rekaman lain.

Seperti yang telah dikatakan, C++ tidak memaksakan struktur apapun pada sebuah file. Jadi, aplikasi yang ingin menggunakan file akses-acak harus diciptakan sendiri oleh pengguna. Berbagai teknik telah digunakan selama ini. Metode termudah untuk mewujudkannya adalah bahwa semua rekaman di dalam file harus sama panjang. Dengan rekaman panjang sama, program semakin mudah untuk menghitung lokasi persis dari sembarang rekaman yang relatif terhadap awal file. Akan ditunjukkan bagaimana hal ini memfasilitasi akses segera terhadap rekaman tertentu, bahkan bila di dalam file besar.

Gambar 9.8 mengilustrasikan cara pandang C++ terhadap file akses-acak yang memuat rekaman-rekaman dengan panjang sama (setiap rekaman, pada kasus ini, memiliki panjang 100 byte). File akses-acak seperti jalan raya dimana banyak kendaraan berukuran sama berjejer, beberapa kendaraan berpenumpang tetapi lainnya kosong.

Gambar 9.8 Cara pandang C++ atas file akses-acak


Data dapat disisipkan ke dalam sebuah file akses-acak tanpa harus merusak data lain di dalam file. Data yang disimpan sebelumnya juga dapat diperbarui atau diharus tanpa perlu menulis-ulang keseluruhan file. Selanjutnya akan dijelaskan bagaimana menciptakan sebuah file akses-acak, mengentrika data ke dalam file, membaca data secara sekuensial dan secara acak, memperbarui data, dan menghapus data yang tidak lagi dibutuhkan.

9.5 Menciptakan File Akses-Acak
Fungsi anggota write pada kelas ostream mengeluarkan sejumlah tetap byte, dimulai dari lokasi tertentu di dalam memori, ke aliran yang dispesifikasi. Ketika aliran diasosiasikan dengan sebuah file, fungsi write menulis data pada lokasi di dalam file yang dispesifikasi oleh pointer put.  Fungsi anggota read pada kelas istream memasukkan sejumlah tetap byte dari aliran yang dispesifikasi ke suatu area di dalam memori dimulai dari alamat tertentu. Jika aliran diasosiasikan dengan sebuah file, maka fungsi read memasukkan byte-byte pada lokasi di dalam file terspesifikasi oleh pointer get.

Menuliskan Byte-Byte dengan Fungsi Anggota write
Ketika menuliskan integer angka ke dalam sebuah file, daripada menggunakan statemen

keluarFile << angka;

dimana untuk integer empat byte ditampilkan sedikit-dikitnya satu dijit dan sebanyak-banyaknya 11 dijit (10 dijit ditambah tanda positif/negatif), lebih baik Anda menggunakan statemen

keluarFile.write( reinterpret_cast< const char * >( &angka ),
  sizeof( angka ) );

yang selalu menuliskan versi biner atas integer empat byte, angka. Fungsi write memperlakukan argumennya sebagai sekelompok byte dengan memandang objek di dalam memori sebagai sebuah const char * (pointer yang menunjuk ke sebuah byte). Mulai dari lokasi tersebut, fungsi write mengeluarkan sejumlah byte yang dispesifikasi oleh argumen keduanya, yaitu sebuah integer bertipe size_t. Seperti yang nanti Anda lihat, fungsi read dari kelas istream dapat digunakan untuk membaca secara berurutan empat byte ke arah variabel integer angka

Mengkonversi antara Tipe Pointer dengan Operator reinterpret_cast
Sayangnya, kebanyakan pointer yang dilewatkan kepada fungsi write sebagai argumen pertama bukanlah bertipe const char *. Untuk mengeluarkan objek bertipe lain, Anda harus mengkonversi pointer yang menunjuk ke objek itu menjadi tipe cons char *. Jika tidak, kompiler tidak akan mengkompilasi pemanggilan terhadap fungsi write. C++ menyediakan operator reinterpret_cast untuk kasus semacam ini. Tanpa reinterpret_cast, statemen write yang mengeluarkan integer angka tidak akan dikompilasi karena kompiler tidak mengijinkan pointer bertipe int * (tipe yang dijadikan nilai balik oleh ekspresi &angka) dilewatkan kepada sebuah fungsi yang mengharapkan argumen bertipe const char *.

Operator reinterpret_cast dilakukan pada saat kompilasi dan tidak mengubah nilai objek yang ditunjuk oleh operandnya. Operator ini meminta kompiler untuk menginterpretasi-ulang operand sebagai tipe target (dispesifikasi di dalam kurung siku yang mengikuti katakunci reinterpret_cast). Pada Gambar 9.11, Anda menggunakan reinterpret_cast untuk mengkonversi sebuah pointer DataKlien menjadi sebuah const char *, yang menginterpretasi-ulang sebuah objek DataKlien sebagai byte-byte yang akan dikeluarkan ke dalam sebuah file. Program pemrosesan file akes-acak jarang sekali ditulis di dalam satu file. Seperti yang didemonstrasikan pada contoh ini.

Program Pemrosesan Kredit
Perhatikan statemen permasalahan berikut:

Ciptakan sebuah program pemrosesan kredit yang mampu menyimpan sebanyak-banyaknya 100 rekaman dengan panjang-tetap untuk sebuah perusahaan yang dapat memiliki sampai 100 pelanggan. Setiap rekaman harus memuat nomor akun yang berperan sebagai kunci rekaman, nama akhir, nama pertama, dan saldo. Program yang dibuat harus dapat memperbarui akun, menyisipkan akun baru, menghapus akun, dan menyisipkan semua rekaman akun ke dalam sebuah file teks terformat untuk dicetak atau ditampilkan.

Pada beberapa bagian selanjutnya, akan dikenalkan beberapa teknik untuk menciptakan program pemrosesan kredit. Gambar 9.11 mengilustrasikan pembukaan sebuah file akses-acak, mendefinisikan format rekaman menggunakan sebuah objek dari kelas DataKlien (Gambar 9.9 – 9.10) dan menuliskan data ke dalam disk dalam format biner. Program ini menginisialisasi semua 100 rekaman pada file kredit.dat dengan objek-objek kosong, menggunakan fungsi write. Setiap objek kosong memuat 0 untuk nomor akun, string null (yang direpresentasikan dengan tanda kutip kosong) untuk nama akhir dan nama pertama, dan 0.0 untuk saldo. Setiap rekaman diinisialisasi dengan sejumlah memori kosong dimana di dalamnya data akun akan disimpan.

Gambar 9.9 Definisi Kelas DataKlien
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Gambar 9.9: DataKlien.h
// Definisi kelas DataKlien digunakan di dalam Gambar 9.11 - 9.14.
#ifndef DATAKLIEN_H
#define DATAKLIEN_H

#include <string>
using namespace std;

class DataKlien
{
public:
  // konstruktor default DataKlien
  DataKlien( int = 0, string = "", string = "", double = 0.0 );

  // fungsi aksesor untuk nomorAkun
  void setNomorAkun( int );
  int getNomorAkun() const;

  // fungsi aksesor untuk namaAkhir
  void setNamaAkhir( string );
  string getNamaAkhir() const;

  // fungsi aksesor untuk namaPertama
  void setNamaPertama( string );
  string getNamaPertama() const;

  // fungsi aksesor untuk saldo
  void setSaldo( double );
  double getSaldo() const;
private:
  int nomorAkun;
  char namaAkhir[ 15 ];
  char namaPertama[ 10 ];
  double saldo;
}; // akhir dari kelas DataKlien

#endif

Gambar 9.10 Kelas DataKlien Menyimpan Informasi Kredit Pelanggan
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// Gambar 9.10: DataKlien.cpp
// Kelas DataKlien menyimpan informasi kredit pelanggan.
#include <string>
#include "DataKlien.h"
using namespace std;

// konstruktor DataKlien default
DataKlien::DataKlien( int nilaiNomorAkun,
  string nilaiNamaAKhir, string nilaiNamaPertama, double nilaiSaldo )
{
  setNomorAkun( nilaiNomorAkun );
  setNamaAkhir( nilaiNamaAKhir );
  setNamaPertama( nilaiNamaPertama );
  setSaldo( nilaiSaldo );
} // akhir dari DataKlien

// mendapatkan nilai nomor-akun
int DataKlien::getNomorAkun() const
{
  return nomorAkun;
} // akhir dari fungsi getNomorAkun

// menetapkan nilai nomor-akun
void DataKlien::setNomorAkun( int nilaiNomorAkun )
{
  nomorAkun = nilaiNomorAkun; // harus divalidasi
} // akhir dari fungsi setNomorAkun

// mendapatkan nama-akhir
string DataKlien::getNamaAkhir() const
{
  return namaAkhir;
} // akhir dari fungsi getNamaAhir

// menetapkan nilai nama-akhir
void DataKlien::setNamaAkhir( string stringNamaAkhir )
{
  // menyalin sebanyak-banyaknya 15 karakter dari string ke namaAkhir
  int panjang = stringNamaAkhir.size();
  panjang = ( panjang < 15 ? panjang : 14 );
  stringNamaAkhir.copy( namaAkhir, panjang );
  namaAkhir[ panjang ] = '\0'; // menyambung karakter null ke dalam namaAkhir
} // akhir dari fungsi setNamaAkhir

// mendapatkan nilai nama-pertama
string DataKlien::getNamaPertama() const
{
  return namaPertama;
} // akhir dari fungsi getNamaPertama

// menetapkan nilai nama-pertama
void DataKlien::setNamaPertama( string stringNamaPertama )
{
  // menyalin sebanyak-banyaknya 15 karakter dari string ke namaPertama
  int panjang = stringNamaPertama.size();
  panjang = ( panjang < 10 ? panjang : 9 );
  stringNamaPertama.copy( namaPertama, panjang );
  namaPertama[ panjang ] = '\0'; // menyambung karakter null ke dalam namaPertama
} // akhir dari fungsi setNamaPertama

// mendapatkan nilai saldo
double DataKlien::getSaldo() const
{
  return saldo;
} // akhir dari fungsi getSaldo

// menetapkan nilai saldo
void DataKlien::setSaldo( double nilaiSaldo )
{
  saldo = nilaiSaldo;
} // akhir dari fungsi setSaldo

Objek dari kelas string tidak memiliki ukuran seragam, tetapi menggunakan memori yang teralokasi secara dinamis untuk mengakomodasi string berbagai panjang. Anda harus menetapkan rekaman panjang-tetap, jadi kelas DataKlien menyimpan nama pertama dan nama akhir seorang pelanggan di dalam array char dengan panjang-tetap (dideklarasikan pada Gambar 9.9, baris 32-33). Fungsi anggota setNamaAkhir (Gambar 9.10, baris 36-43) dan setNamaPertama (Gambar 9.10, baris 52-59) masing-masing menyalin karakter-karakter dari sebuah objek string ke dalam array char. Perhatikan fungsi setNamaAkhir. Baris 39 memanggil fungsi anggota size (dari kelas string) untuk mendapatkan panjang atas stringNamaAkhir. Baris 40 memastikan bahwa panjang lebih kecil dari 15 karakter, kemudian baris 41 menyalin sebanyak panjang karakter dari stringNamaAkhir ke dalam array char, namaAkhir, menggunakan fungsi anggota copy. Fungsi anggota setNamaPertama melakukan langkah-langkah yang sama untuk nama pertama.

Gambar 9.11, baris 11 menciptakan sebuah objek ofstream untuk file kredit.dat. Argumen kedua untuk konstruktor, ios::out|ios::binary, mengindikasikan bahwa Anda sedang membuka file untuk keluaran dalam mode biner, yang diperlukan jika Anda akan menulis rekaman dengan panjang-tetap. Baris 24-25 menyebabkan klienKosong untuk dituliskan ke dalam file kredit.dat, yang diasosiasikan dengan objek ofstream, keluarKredit. Ingat bahwa operator sizeof menghasilkan nilai balik berupa ukuran dalam byte atas objek yang diapit kurung. Argumen pertama pada fungsi write pada baris 24 harus bertipe const char *. Namun, tipe data dari &klienKosong adalah DataKlien *. Untuk mengkonversi &klienKosong menjadi const char *, baris 24 menggunakan operator reinterpret_cast, sehingga pemanggilan terhadap fungsi write dapat dikompilasi tanpa menyebabkan error kompilasi.

Gambar 9.11 Menciptakan Sebuah File Akses_Acak

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Gambar 9.11: gambar9_11.cpp
// Menciptakan sebuah file yang diakses secara acak.
#include <iostream>
#include <fstream>
#include <cstdlib>
#include "DataKlien.h" // definisi kelas DataKlien
using namespace std;

int main()
{
  ofstream keluarKredit( "kredit.dat", ios::out | ios::binary );

  // keluar program jika ofstream tidak dapat membuka file
  if ( !keluarKredit )
  {
    cerr << "File tidak dapat dibuka." << endl;
    exit( 1 );
  } // akhir dari if

  DataKlien klienKosong; // konstruktor mengnolkan setiap anggota data

  // mengeluarkan 100 rekaman kosong ke file
  for ( int i = 0; i < 100; ++i )
    keluarKredit.write( reinterpret_cast< const char * >( &klienKosong ),
      sizeof( DataKlien ) );
} // akhir dari main


9.6 Menuliskan Data Secara Acak ke dalam File Akses-Acak
Gambar 9.12 menuliskan data ke dalam file kredit.dat dan menggunakan kombinasi dari fungsi seekp dan write untuk menyimpan data pada lokasi-lokasi yang spesifik di dalam file. Fungsi seekp menetapkan pointer put untuk menunjuk ke posisi tertentu di dalam file, kemudian fungsi write mengeluarkan data. Baris 6 mencantumkan header DataKlien.h yang didefinisikan di dalam Gambar 9.9, sehingga program dapat menggunakan objek-objek DataKlien.

Gambar 9.12 Kelas DataKlien Menyimpan Informasi Kredit Pelanggan
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// Gambar 9.12: gambar9_12.cpp
// Menulis ke dalam file akses-acak.
#include <iostream>
#include <fstream>
#include <stdlib>
#include "DataKlien.h" // definisi kelas DataKlien
using namespace std;

int main()
{
  int nomorAkun;
  string namaAkhir;
  string namaPertama;
  double saldo;

  fstream keluarKredit( "kredit.dat", ios::in | ios::out | ios::binary );

  // keluar program jika fstream tidak dapat membuka file
  if ( !keluarKredit )
  {
    cerr << "File tidak dapat dibuka." << endl;
    exit( 1 );
  } // akhir dari if

  cout << "Masukkan nomor akun (1 sampai 100, 0 untuk mengakhiri masukan)\n? ";

  // meminta pengguna untuk menspesifikasi nomor akun
  DataKlien klien;
  cin >> nomorAkun;

  // pengguna memasukkan informasi, yang disalin ke dalam file
  while ( nomorAkun > 0 && nomorAkun <= 100 )
  {
    // pengguna memasukkan nama akhir, nama pertama, dan saldo
    cout << "Masukkan nama pertama, nama akhir, dan saldo\n? ";
    cin >> namaAkhir;
    cin >> namaPertama;
    cin >> saldo;

   // menetapkan rekaman nomorAkun, namaAkhir, namaPertama, dan saldo
    klien.setNomorAkun( nomorAkun );
    klien.setNamaAkhir( namaAkhir );
    klien.setNamaPertama( namaPertama );
    klien.setSaldo( saldo );

    // mencari posisi di dalam file dari rekaman
    keluarKredit.seekp( ( klien.getNomorAkun() - 1 ) *
      sizeof( DataKlien ) );

    // menulis informasi di dalam file
    keluarKredit.write( reinterpret_cast< const char * >( &klien ),
      sizeof( DataKlien ) );

    // memampukan pengguna untuk memasukkan akun lain
    cout << "Masukkan nomor akun\n? ";
    cin >> nomorAkun;
  } // akhir dari while
} // akhir dari main

Masukkan nomor akun (1 sampai 100, 0 untuk mengakhiri masukan)
? 37
Masukkan nama akhir, nama pertama, dan saldo
? Tohonan Robert 0.00
Masukkan nomor akun
? 29
Masukkan nama akhir, nama pertama, dan saldo
? Chandra Rico -24.54
Masukkan nomor akun
? 96
Masukkan nama akhir, nama pertama, dan saldo
? Rotua Marolop 34.98
Masukkan nomor akun
? 88
Masukkan nama akhir, nama pertama, dan saldo
? Duma Eva 258.34
Masukkan nomor akun
? 33
Masukkan nama akhir, nama pertama, dan saldo
? Meika Rini 314.33
Masukkan nomor akun
? 0

Baris 47-48 memposisikan pointer put untuk objek keluarKredit agar menunjuk ke lokasi byte yang dihitung dengan

keluarKredit.seekp( ( klien.getNomorAkun() - 1 ) * sizeof( DataKlien ) );

Karena nomor akun berada di antara 1 dan 100, 1 akan dikurangkan dari nomor akun ketikan menghitung lokasi byte dari rekaman. Jadi, untuk rekaman 1, pointer put ditetapkan menunjuk ke byte 0 di dalam file. Baris 16 menggunakan objek fstream, keluarKredit, untuk membuka file kredit.dat yang sudah ada. File dibuka untuk masukan dan keluaran dalam mode biner dengan mengkombinasikan mode file-open ios::in, ios::out, dan ios::binary. Mode jamak ini dikombinasikan dengan memisahkan setiap mode menggunakan operator OR bitwise (|). Pembukaan file yang sudah ada, kredit.dat, dengan cara ini memastikan bahwa program ini dapat memanipulasi rekaman yang ditulis ke dalam file oleh program pada Gambar 9.11.

9.7 Membaca dari File Akses-Acak Secara Sekuensial
Pada bagian sebelumnya, Anda telah menciptakan sebuah file akses-acak dan menuliskan data ke dalam file tersebut. Pada bagian ini, akan dikembangkan sebuah program yang membaca file secara sekuensial dan menampilkan hanya rekaman-rekaman yang memuat data. Program ini menghasilkan keuntungan tambahan. Pastikan Anda mengetahui keuntungan tersebut di akhir bagian ini.

Fungsi read dari kelas istream memasukkan sejumlah byte tertentu dari posisi sekarang di dalam aliran tertentu ke dalam sebuah objek. Sebagai contoh, baris 30-31 dari Gambar 9.13 membaca sejumlah byte yang dispesifikasi oleh sizeof(DataKlien) dari file yang diasosiasikan dengan objek masukKredit dan menyimpan data di dalam rekaman klien. Fungsi read memerlukan argumen pertama bertipe char *. Karena &klien bertipe DataKlien *, &klien harus dicast menjadi char * menggunakan operator reinterpret_cast.

Gambar 9.13 Membaca File Akses-Acak Secara Sekuensial
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Gambar 9.13: gambar9_13.cpp
// Membaca file akses-acak secara sekuensial.
#include <iostream>
#include <iomanip>
#include <fstream>
#include <cstdlib>
#include "DataKlien.h" // definisi kelas DataKlien
using namespace std;

void keluaranBaris( ostream&, const DataKlien & ); // prototipe

int main()
{
  ifstream masukKredit( "kredit.dat", ios::in | ios::binary );

  // keluar program jika ifstream tidak bisa membuka file
  if ( !masukKredit )
  {
    cerr << "File tidak bisa dibuka." << endl;
    exit( 1 );
  } // akhir dari if

  cout << left << setw( 10 ) << "Akun" << setw( 16 )
    << "Nama Akhir" << setw( 11 ) << "Nama Pertama" << left
    << setw( 10 ) << right << "Saldo" << endl;

  DataKlien klien; // menciptakan rekaman

  // membaca rekaman pertama dari file
  masukKredit.read( reinterpret_cast< char * >( &klien ),
    sizeof( DataKlien ) );

  // membaca semua rekaman dari file
  while( masukKredit && !masukKredit.eof() )
  {
    // menampilkan rekaman
    if ( klien.getNomorAkun() != 0 )
      keluaranBaris( cout, klien );

    // membaca berikutnya dari file
    masukKredit.read( reinterpret_cast< char * >( &klien ),
      sizeof( DataKlien ) );
  } // akhir dari while
} // akhir dari main

// menampilkan satu rekaman
void keluaranBaris( ostream &keluaran, const DataKlien &rekaman )
{
  keluaran << left << setw( 10 ) << rekaman.getNomorAkun()
    << setw( 16 ) << rekaman.getNamaAkhir()
    << setw( 11 ) << rekaman.getNamaPertama()
    << setw( 10 ) << setprecision( 2 ) << right << fixed
    << showpoint << rekaman.getSaldo() << endl;
} // akhir dari fungsi keluaranBaris

Akun            Nama Akhir      Nama Pertama    Saldo
29              Chandra         Rico            -24.54
33              Meika           Rini            314.33
37              Tohonan         Robert             0.00
88              Duma            Eva             258.34
96              Rotua           Marolop         34.98

Gambar 9.13 membaca setiap rekaman di dalam file kredit.dat secara sekuensial, memeriksa setiap rekaman untuk menentukan apakah memuat data atau tidak, dan menampilkan keluaran terformat untuk rekaman-rekaman yang memuat data. Kondisi pada baris 34 menggunakan fungsi anggota eof (dari kelas ios) untuk menentukan kapan end-of-file dijumpai dan menyebabkan eksekusi atas statemen while berhenti. Juga, jika suatu error terjadi ketika membaca dari file, loop akan berhenti, karena evaluasi terhadap masukKredit bernilai false. Data yang dimasukkan dari file ditampilkan oleh fungsi keluaranBaris (baris 47-54), yang memerlukan dua argumen, suatu objek ostream dan sebuah struktur DataKlien. Tipe parameter ostream ini menarik, karena sembarang objek ostream (seperti cout) atau sembarang objek yang diderivasi dari kelas ostream (seperti objek bertipe ofstream) dapat disuplai sebagai argumen.

9.8 Studi Kasus: Program Pemrosesan Transaksi
Sekarang akan disajikan sebuah program pemrosesan transaksi (Gambar 9.14) menggunakan suatu file akses-acak untuk melakukan pemrosesan secara instan. Program menetapkan informasi akun bank. Program juga memperbarui akun yang sudah ada, menambah akun baru, menghapus akun, dan menyimpan daftar terformat dari semua akun di dalam suatu file teks. Diasumsukan bahwa program pada Gambar 9.11 telah dieksekusi untuk menciptakan file kredit.dat dan bahwa program pada Gambar 9.12 telah dieksekusi untuk menyisipkan data awal.

Gambar 9.14 Membaca, Memperbarui, Menyisipkan, dan Menghapus
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
// Gambar 9.14: gambar9_14.cpp
// Program ini membaca sebuah file akses-acak secara sekuensial, memperbarui
// data yang sebelumnya ditulis ke dalam file, menciptakan data yang akan ditempatkan
// ke dalam file, dan menghapus data yang sebelumnya disimpan di dalam file.
#include <iostream>
#include <fstream>
#include <iomanip>
#include <cstdlib>
#include "DataKlien.h" // definisi kelas DataKlien
using namespace std;

int masukkanPilihan();
void ciptakanFileTeks( fstream& );
void perbaruiRekaman( fstream& );
void baruRekaman( fstream& );
void hapusRekaman( fstream& );
void keluaranBaris( ostream&, const DataKlien & );
int getAkun( const char * const );

enum Pilihan { TAMPIL = 1, PERBARUI, BARU, HAPUS, AKHIR };

int main()
{
  // membuka file untuk pembacaan dan penulisan
  fstream masukKeluarKredit( "kredit.dat", ios::in | ios::out | ios::binary );

  // keluar program jika fstream tidak bisa membuka file
  if( !masukKeluarKredit )
  {
    cerr << "File tidak dapat bisa dibuka." << endl;
    exit ( 1 );
  } // akhir dari if

  int pilihan; // menyimpan pilihan pengguna

  // memampukan pengguna untuk menentukan aksi
  while ( ( pilihan = masukkanPilihan() ) != AKHIR )
  {
    switch ( pilihan )
    {
      case TAMPIL: // menciptakan file teks dari file rekaman
        ciptakanFileTeks( masukKeluarKredit );
        break;
      case PERBARUI: // memperbarui rekaman
        perbaruiRekaman( masukKeluarKredit );
        break;
      case BARU: // menciptakan rekaman
        baruRekaman( masukKeluarKredit );
        break;
      case HAPUS: // menghapus rekaman yang ada
        hapusRekaman( masukKeluarKredit );
        break;
      default: // menampilkan error jika pengguna tidak memilih pilihan
        cerr << "Pilihan salah" << endl;
        break;
    } // akhir dari switch

    masukKeluarKredit.clear(); // mereset indikator end-of-file
  } // akhir dari while
} // akhir dari main

// memampukan pengguna memasukkan menu pilihan
int masukkanPilihan()
{
  // menampilkan opsi yang tersedia
  cout << "\nMasukkan pilihan Anda" << endl
    << "1 - simpan file teks terformat yang memuat akun" << endl
    << "  dinamakan \"tampil.txt\" untuk ditampilkan" << endl
    << "2 - perbarui sebuah akun" << endl
    << "3 - tambah sebuah akun baru" << endl
    << "4 - hapus sebuah akun" << endl
    << "5 - akhiri program\n? ";

  int menuPilihan;
  cin >> menuPilihan; // memasukkan pilihan menu dari pengguna
  return menuPilihan;
} // akhir dari fungsi masukkanPilihan

// menciptakan file teks terformat untuk ditampilkan
void ciptakanFileTeks( fstream &bacaDariFile )
{
  // menciptakan file teks
  ofstream keluarFileTampil( "tampil.txt", ios::out );

  // keluar program jika ofstream tidak bisa menciptakan file
  if( !keluarFileTampil )
  {
    cerr << "File tidak bisa diciptakan." << endl;
    exit( 1 );
  } // akhir dari if

  keluarFileTampil << left << setw( 10 ) << "Akun" << setw( 16 )
    << "Nama Akhir" << setw( 11 ) << "Nama Pertama" << right
    << setw( 10 ) << "Saldo" << endl;

  // menetapkan pointer posisi ke awal bacaDariFile
  bacaDariFile.seekg( 0 );

  // membaca rekaman pertama dari file rekaman
  DataKlien klien;
  bacaDariFile.read( reinterpret_cast< char * >( &klien ),
    sizeof( DataKlien ) );

  // menyalin semua rekaman dari file rekaman ke dalam file teks
  while( !bacaDariFile.eof() )
  {
    // menuliskan satu rekaman ke dalam file teks
    if ( klien.getNomorAkun() != 0 ) // lompati rekaman kosong
      keluaranBaris( keluarFileTampil, klien );

    // membaca rekaman berikutnya dari file rekaman
    bacaDariFile.read( reinterpret_cast< char * >( &klien ),
      sizeof( DataKlien ) );
  } // akhir dari while
} // akhir dari fungsi ciptakanFileTeks

// memperbarui saldo di dalam rekaman
void perbaruiRekaman( fstream &perbaruiFile )
{
  // mendapatkan nomor akun yang akan diperbarui
  int nomorAkun = getAkun( "Masukkan akun yang akan diperbarui" );
  
  // menggerakkan pointer posisi ke rekaman yang tepat di dalam file
  perbaruiFile.seekg( ( nomorAkun - 1 ) * sizeof( DataKlien ) );

  // membaca rekaman pertama dari file
  DataKlien klien;
  perbaruiFile.read( reinterpret_cast< char * >( &klien ),
    sizeof( DataKlien ) );

  // memperbarui rekaman
  if ( klien.getNomorAkun() != 0 )
  {
    keluaranBaris( cout, klien ); // menampilkan rekaman

    // meminta pengguna untuk menentukan transaksi
    cout << "\nMasukkan biaya (+) atau bayar (-): ";
    double transaksi; // biaya atau bayar
    cin >> transaksi;

    // memperbarui rekaman saldo
    double saldoLama = klien.getSaldo();
    klien.setSaldo( saldoLama + transaksi );
    keluaranBaris( cout, klien ); // menampilkan rekaman

    // menggerakkan pointer posisi ke rekaman yang sesuai di dalam file
    perbaruiFile.seekp( ( nomorAkun - 1 ) * sizeof( DataKlien ) );

    // menulis rekaman terperbarui menggantikan rekaman lama di dalam file
    perbaruiFile.write( reinterpret_cast< const char * >( &klien ),
      sizeof( DataKlien ) );
  } // akhir dari if
  else // menampilkan error jika akun tidak ada
    cerr << "Akun #" << nomorAkun
      << " tidak memiliki informasi." << endl;
} // akhir dari fungsi perbaruiRekaman

// menciptakan dan menyisipkan rekaman
void baruRekaman( fstream &sisipDalamFile )
{
  // mendapatkan nomor akun untuk diciptakan
  int nomorAkun = getAkun( "Masukkan nomor akun baru" );

  // menggerakkan pointer posisi ke rekaman yang tepat di dalam file
  sisipDalamFile.seekg( ( nomorAkun - 1 ) * sizeof( DataKlien ) );

  // membaca rekaman dari file
  DataKlien klien;
  sisipDalamFile.read( reinterpret_cast< char * >( &klien ),
    sizeof( DataKlien ) );

  // menciptakan rekaman, jika rekaman belum ada sebelumnya
  if ( klien.getNomorAkun() == 0 )
  {
    string namaAkhir;
    string namaPertama;
    double saldo;

    // pengguna memasukkan nama akhir, nama pertama, dan saldo
    cout << "Masukkan nama akhir, nama pertama, dan saldo\n? ";
    cin >> setw( 15 ) >> namaAkhir;
    cin >> setw( 10 ) >> namaPertama;
    cin >> saldo;

    // menggunakan nilai-nilai untuk menginisi nilai-nilai akun
    klien.setNamaAkhir( namaAkhir );
    klien.setNamaPertama( namaPertama );
    klien.setSaldo( saldo );
    klien.setNomorAkun( nomorAkun );

    // menggerakkan pointer posisi ke rekaman yang tepat di dalam file
    sisipDalamFile.seekp( ( nomorAkun - 1 ) * sizeof( DataKlien ) );

    // menyisikan rekaman di dalam file
    sisipDalamFile.write( reinterpret_cast< const char * >( &klien ),
      sizeof( DataKlien ) );
  } // akhir dari if
  else // menampilkan error jika akun sudah ada
    cerr << "Akun #" << nomorAkun
      << " sudah memiliki informasi." << endl;
} // akhir dari fungsi baruRekaman

// menghapus rekaman yang sudah ada
void hapusRekaman( fstream &hapusDariFile )
{
  // mendapatkan nomor akun yang akan dihapus
  int nomorAkun = getAkun( "Masukkan akun yang akan dihapus" );

  // menggerakkan pointer posisi ke rekaman yang tepat di dalam file
  hapusDariFile.seekg( ( nomorAkun - 1 ) * sizeof( DataKlien ) );

  // membaca rekaman dari file
  DataKlien klien;
  hapusDariFile.read( reinterpret_cast< char * >( &klien ),
    sizeof( DataKlien ) );

  // menghapus rekaman, jika rekaman sudah ada di dalam file
  if ( klien.getNomorAkun() != 0 )
  {
    DataKlien kosongKlien; // menciptakan rekaman kosong

    // menggerakkan pointer posisi ke rekaman yang tepat di dalam file
    hapusDariFile.seekp( ( nomorAkun - 1 ) *
      sizeof( DataKlien ) );

    // mengganti rekaman yang sudah ada dengan rekaman kosong
    hapusDariFile.write(
      reinterpret_cast< const char * >( &kosongKlien ),
        sizeof( DataKlien ) );

    cout << "Akun #" << nomorAkun << " dihapus.\n";
  } // akhir dari if
  else // menampilkan error jika rekaman tidak ada
    cerr << "Akun #" << nomorAkun << " kosong.\n";
} // akhir dari hapusRekaman

// menampilkan satu rekaman
void keluaranBaris( ostream &keluaran, const DataKlien &rekaman )
{
  keluaran << left << setw( 10 ) << rekaman.getNomorAkun()
    << setw( 16 ) << rekaman.getNamaAkhir()
    << setw( 11 ) << rekaman.getNamaPertama()
    << setw( 10 ) << setprecision( 2 ) << right << fixed
    << showpoint << rekaman.getSaldo() << endl;
} // akhir dari fungsi keluaranBaris

 // mendapatkan nilai nomor-akun dari pengguna
int getAkun( const char * const prompt )
{
  int nomorAkun;

  // mendapatkan nilai nomor-akun
  do
  {
    cout << prompt << " (1 - 100): ";
    cin >> nomorAkun;
  } while ( nomorAkun < 1 || nomorAkun > 100 );

  return nomorAkun;
} // akhir dari fungsi getAkun

Program mempunyai lima opsi (opsi 5 diberikan untuk menghentikan program). Opsi 1 memanggil fungsi ciptakanFileTeks untuk menyimpan daftar terformat atas semua informasi akun di dalam sebuah file teks yang dinamakan tampil.txt. Fungsi ciptakanFileTeks (baris 80 – 115) mengambil sebuah objek fstream sebagai argumen untuk digunakan dalam memasukkan data dari file kredit.dat. Fungsi ciptakanFileTeks memanggil fungsi anggota read dari kelas istream (baris 101-102) dan menggunakan teknik file-akses sekuensial pada Gambar 9.13 untuk mengeluarkan data ke file tampil.txt. Perhatikan bahwa ciptakanFileTeks menggunakan fungsi anggota seekg dari kelas istream (baris 97) untuk memastikan bahwa pointer posisi ditempatkan di awal file. Setelah memilih opsi 1, file tampil.txt memuat

Akun            Nama Akhir      Nama Pertama    Saldo
29              Chandra         Rico            -24.54
33              Meika           Rini            314.33
37              Tohonan         Robert             0.00
88              Duma            Eva             258.34
96              Rotua           Marolop         34.98

Opsi 2 memanggil perbaruiRekaman (baris 118 – 156) untuk memperbarui sebuah rekaman. Fungsi ini hanya memperbarui rekaman yang sudah ada, jadi fungsi ini pertama-tama menentukan apakah rekaman yang dispesifikasi kosong atau tidak. Baris 128 – 129 membaca data ke dalam objek klien, menggunakan fungsi anggota read dari kelas istream. Kemudian baris 132 membandingkan nilai yang dijadikan nilai balik oleh getNomorAkun dari objek klien dengan nol untuk menentukan apakah rekaman memuat informasi atau tidak. Jika nilai tersebut nol, maka baris 154-155 menampilkan pesan error yang mengindikasikan bahwa rekaman kosong. Jika rekaman memuat informasi, maka baris 134 menampilkan rekaman menggunakan fungsi keluaranBaris, baris 139 memasukkan jumlah transaksi dan baris 142-151 menghitung saldo baru, dan menulis-ulang rekaman ke dalam file. Keluaran untuk opsi adalah

Masukkan akun untuk diperbarui (1 - 100): 37
37              Tohonan         Robert          0.00

Masukkan biaya (+) atau bayar (-): +87.99
37              Tohonan         Robert          87.99

Opsi 3 memanggil fungsi baruRekaman (baris 159-201) untuk menambahkan sebuah rekaman baru ke dalam file. Jika pengguna memasukkan sebuah nomor akun untuk akun yang sudah ada, baruRekaman menampilkan pesan error yang mengindikasikan bahwa akun sudah ada (baris 199-200). Keluaran untuk opsi 3 adalah

Masukkan nomor akun baru (1-100): 22
Masukkan nama akhir, nama pertama, dan saldo
? Pratama Jodi 247.45

Opsi memanggil fungsi hapusRekaman (baris 204-235) untuk menghapus sebuah rekaman dari file. Baris 207 mendesak pengguna untuk memasukkan nomor akun. Hanya rekaman yang sudah ada yang akan dihapus, jadi, jika akun yang dispesifikasi kosong, maka baris 234 akan menampilkan pesan error. Jika akun sudah ada, baris 227-229 menginisialisasi-ulang akun tersebut dengan menyalin sebuah rekaman kosong (kosongKlien) ke dalam file tersebut. Baris 231 menampilkan pesan untuk menginformasikan pengguna bahwa rekaman telah dihapus. Keluaran untuk opsi 4 adalah

Masukkkan akun untuk dihapus (1-100): 29
Akun #29 dihapus.

Baris 25 membuka file kredit.dat dengan menciptakan sebuah objek fstream untuk pembacaan dan penulisan, menggunakan mode ios::in dan ios::out.

Kesimpulan
*        C++ memandang bahwa setiap file hanyalah sebuah runtun byte. Setiap file diakhiri dengan penanda end-of-file atau sejumlah byte tertentu yang direkam di dalam suatu struktur data sistem operasi. Ketika sebuah file dibuka, suatu objek diciptakan, dan aliran diasosiasikan dengan objek tersebut. Anda telah melihat bahwa objek cin, cout, cerr, dan clog diciptakan ketika <iostream> dicantumkan. Aliran yang diasosiasikan dengan objek menyediakan kanal komunikasi antara program dan file atau divais tertentu. Sebagai contoh, objek cin (objek aliran masukan standard) memampukan program untuk memasukkan data dari papanketik atau dari divais lainnya, objek cout (objek aliran keluaran standard) memampukan program untuk mengeluarkan data ke layar atau divais lainnya, dan objek cerr dan clog (objek aliran error standard) memampukan program untuk mengeluarkan error ke layar atau ke divais lainnya.
*      Seperti dinyatakan sebelumnya, file dibuka dengan menciptakan objek ifstream, ofstream, dan fstream. File dibuka untuk keluaran, sehingga objek ofstream yang diciptakan. Untuk suatu objek ofstream, mode file-open dapat berupa ios::out untuk mengeluarkan data ke sebuah file atau berupa ios::app untuk menyambungkan data ke akhir suatu file (tanpa memodifikasi data yang sebelumnya sudah ada di dalam file itu). Data di dalam file yang sudah ada bila dibuka dengan mode ios::out akan dihapus. Jika file yang dispesifikasi belum ada, maka objek ofstream akan menciptakan file tersebut dengan nama yang diberikan.
*         Fungsi anggota write pada kelas ostream mengeluarkan sejumlah tetap byte, dimulai dari lokasi tertentu di dalam memori, ke aliran yang dispesifikasi. Ketika aliran diasosiasikan dengan sebuah file, fungsi write menulis data pada lokasi di dalam file yang dispesifikasi oleh pointer put.  Fungsi anggota read pada kelas istream memasukkan sejumlah tetap byte dari aliran yang dispesifikasi ke suatu area di dalam memori dimulai dari alamat tertentu. Jika aliran diasosiasikan dengan sebuah file, maka fungsi read memasukkan byte-byte pada lokasi di dalam file terspesifikasi oleh pointer get.
Latihan
1)      Asumsikan bahwa setiap statemen berikut dapat diterapkan pada program yang sama.
a)      Tuliskan sebuah statemen yang membuka file file_lama.dat untuk masukan; gunakan objek ifstream yang dinamakan masukFileLama.
b)      Tuliskan sebuah statemen yang membuka file trans_file.dat; gunakan objek ifstream yang dinama masukTrans.
c)      Tuliskan sebuah statemen yang membuka file file_baru.dat; gunakan objek ofstream yang dinamakan keluarFileBaru.
d)      Tuliskan sebuah statemen yang membaca sebuah rekaman dari file file_lama.dat. Rekaman memuat integer nomorAkun, string nama, dan nilai pecahan saldoSekarang; gunakan objek ifstream yang dinamakan masukFileLama.
e)      Tuliskan sebuah statemen yang membaca sebuah rekaman dari file trans_file.dat. Rekaman memuat integer nomorAkun dan nilai pecahan jumlahDollar; gunakan objek ifstream yang dinamakan masukTrans.
f)        Tuliskan sebuah statemen yang menulis sebuah rekaman ke dalam file file_baru.dat. Rekaman memuat integer nomorAkun, string nama, dan nilai pecahan saldoSekarang; gunakan objek ostream yang dinamakan keluarFileBaru.

2)      Tuliskan sederet statemen yang melakukan hal-hal berikut. Asumsikan bahwa Anda telah mendefinisikan kelas Orang yang memuat anggota data private
char namaAkhir[15];
char namaPertama[10];
int usia;
int id;

            dan fungsi-fungsi anggota public
           
// fungsi aksesor untuk id
void setId( int );
int getId() const;

fungsi aksesor untuk namaTerakhir

void setNamaTerakhir( string );
string getNamaTerakhir() const;

fungsi aksesor untuk namaPertama

void setNamaPertama( string );
string getNamaPertama() const;

            Fungsi aksesor untuk usia

void setUsia( int );
int getUsia) const;

            Juga diasumsikan beberapa file akses-acak telah dibukan secara benar.
a)      Inisialisasilah manusia.dat dengan 100 rekaman yang menyimpan nilai-nilai namaTerakhir = “ “, namaPertama = “ “, dan usia = 0.
b)      Masukkan 10 nama terakhir, nama pertama, dan usia, dan tuliskan ke dalam file.
c)      Perbarui sebuah rekaman yang sudah memuat informasi. Jika rekaman tidak memuat informasi, informasikan kepada pengguna dengan pesan “Tidak ada informasi”.
d)      Hapus sebuah rekaman yang memuat informasi dengan menginisialisasi-ulang rekaman tersebut.







2 comments: