Langkah Berikutnya

Pengantar Pemrograman dan C++

Tutorial online ini berlanjut dengan konsep yang lebih canggih - baca Bagian III. Fokus kita dalam modul ini adalah menggunakan pointer, dan memulai dengan objek.

Belajar dari Contoh #2

Fokus kita dalam modul ini adalah mendapatkan lebih banyak latihan dengan dekomposisi, memahami pointer, dan memulai dengan objek serta class. Ikuti contoh-contoh berikut. Tulis programnya sendiri saat diminta, atau lakukan eksperimen. Kita tidak boleh terlalu menekankan bahwa kunci untuk menjadi programmer yang baik adalah berlatih, berlatih, berlatih!

Contoh #1: Lebih Banyak Praktik Dekomposisi

Pertimbangkan output berikut dari game sederhana:

Welcome to Artillery.
You are in the middle of a war and being charged by thousands of enemies.
You have one cannon, which you can shoot at any angle.
You only have 10 cannonballs for this target..
Let's begin...

The enemy is 507 feet away!!!
What angle? 25<
You over shot by 445
What angle? 15
You over shot by 114
What angle? 10
You under shot by 82
What angle? 12
You under shot by 2
What angle? 12.01
You hit him!!!
It took you 4 shots.
You have killed 1 enemy.
I see another one, are you ready? (Y/N) n

You killed 1 of the enemy.

Pengamatan pertama adalah teks pengantar yang ditampilkan satu kali per eksekusi program. Kita membutuhkan generator angka acak untuk menentukan jarak musuh untuk setiap putaran. Kita memerlukan mekanisme untuk mendapatkan input sudut dari pemain, dan ini jelas berada dalam struktur loop karena terus berulang sampai kita mengenai musuh. Kita juga memerlukan fungsi untuk menghitung jarak dan sudut. Terakhir, kita harus memantau jumlah tembakan yang diperlukan untuk memukul musuh, serta jumlah musuh yang kita tembak selama eksekusi program. Berikut ini garis besar yang memungkinkan untuk program utama.

StartUp(); // This displays the introductory script.
killed = 0;
do {
  killed = Fire(); // Fire() contains the main loop of each round.
  cout << "I see another one, care to shoot again? (Y/N) " << endl;
  cin >> done;
} while (done != 'n');
cout << "You killed " << killed << " of the enemy." << endl;

Prosedur Fire menangani permainan game. Dalam fungsi tersebut, kita memanggil generator angka acak untuk mengetahui jarak musuh, lalu menyiapkan loop untuk mendapatkan input pemain dan menghitung apakah mereka telah mengenai musuh atau belum. Kondisi guard di loop adalah seberapa dekat kita sudah bisa mengenai musuh.

In case you are a little rusty on physics, here are the calculations:

Velocity = 200.0; // initial velocity of 200 ft/sec Gravity = 32.2; // gravity for distance calculation // in_angle is the angle the player has entered, converted to radians. time_in_air = (2.0 * Velocity * sin(in_angle)) / Gravity; distance = round((Velocity * cos(in_angle)) * time_in_air);

Karena panggilan ke cos() dan sin(), Anda perlu menyertakan matematika.h. Cobalah menulis program ini - ini adalah praktik yang baik dalam dekomposisi soal dan ulasan yang baik tentang C++ dasar. Ingatlah untuk hanya melakukan satu tugas di setiap fungsi. Ini adalah program paling canggih yang telah kami tulis sejauh ini, jadi Anda mungkin perlu waktu untuk melakukannya.Berikut adalah solusi kami. 

Contoh #2: Berlatih dengan Pointer

Ada empat hal yang perlu diingat saat menggunakan pointer:
  1. Pointer adalah variabel yang menyimpan alamat memori. Saat program dijalankan, semua variabel disimpan dalam memori, masing-masing di alamat atau lokasi yang unik. Pointer adalah jenis variabel khusus yang berisi alamat memori, bukan nilai data. Sama seperti data yang dimodifikasi saat variabel normal digunakan, nilai alamat yang disimpan dalam pointer juga diubah saat variabel pointer juga dimanipulasi. Berikut contohnya:
    int *intptr; // Declare a pointer that holds the address
                 // of a memory location that can store an integer.
                 // Note the use of * to indicate this is a pointer variable.
    
    intptr = new int; // Allocate memory for the integer.
    *intptr = 5; // Store 5 in the memory address stored in intptr.
          
  2. Kami biasanya mengatakan bahwa pointer "mengarah" ke lokasi yang disimpannya ("pointee"). Jadi dalam contoh di atas, intptr mengarah ke titik 5.

    Perhatikan penggunaan operator "baru" untuk mengalokasikan memori untuk pointee integer. Ini adalah sesuatu yang harus kita lakukan sebelum mencoba mengakses pointee.

    int *ptr; // Declare integer pointer.
    ptr = new int; // Allocate some memory for the integer.
    *ptr = 5; // Dereference to initialize the pointee.
    *ptr = *ptr + 1; // We are dereferencing ptr in order
                     // to add one to the value stored
                     // at the ptr address.
          

    Operator * digunakan untuk dereferensi di C. Salah satu error paling umum yang dilakukan programmer C/C++ saat bekerja dengan pointer adalah lupa menginisialisasi pointee. Hal ini terkadang dapat menyebabkan error runtime karena kita sedang mengakses lokasi dalam memori yang berisi data yang tidak dikenal. Jika kami mencoba dan mengubah data ini, data ini dapat menyebabkan kerusakan memori halus yang membuatnya sulit dilacak. 

  3. Penetapan pointer di antara dua pointer membuat mereka mengarah ke pointee yang sama. Jadi penetapan y = x; membuat y titik ke titik yang sama dengan x. Penetapan pointer tidak menyentuh penerima. Fungsi ini hanya mengubah satu pointer agar memiliki lokasi yang sama dengan pointer lainnya. Setelah penetapan pointer, kedua pointer "membagikan" pointer "berbagi". 
  4. void main() {
     int* x; // Allocate the pointers x and y
     int* y; // (but not the pointees).
    
     x = new int; // Allocate an int pointee and set x to point to it.
    
     *x = 42; // Dereference x and store 42 in its pointee
    
     *y = 13; // CRASH -- y does not have a pointee yet
    
     y = x; // Pointer assignment sets y to point to x's pointee
    
     *y = 13; // Dereference y to store 13 in its (shared) pointee
    }
      

Berikut ini adalah rekaman aktivitas kode ini:

1. Alokasikan dua pointer x dan y. Mengalokasikan pointer tidak akan mengalokasikan pointee apa pun.
2. Alokasikan pointee dan tetapkan x untuk menunjuknya.
3. Batalkan referensi x untuk menyimpan 42 di titik titik acuannya. Ini adalah contoh dasar operasi dereferensi. Mulai di x, ikuti tanda panah untuk mengakses pointee-nya.
4. Cobalah untuk membatalkan referensi y untuk menyimpan 13 di pointee-nya. Ini error karena y tidak memiliki pointee -- ia tidak pernah ditetapkan.
5. Tetapkan y = x; sehingga y mengarah ke arah x. Sekarang x dan y menunjuk ke poin yang sama -- keduanya "berbagi".
6. Cobalah untuk membatalkan referensi y untuk menyimpan 13 di pointee-nya. Kali ini berhasil, karena tugas sebelumnya memberi Anda pointer.

Seperti yang Anda lihat, gambar sangat membantu dalam memahami penggunaan pointer. Berikut contoh lainnya.

int my_int = 46; // Declare a normal integer variable.
                 // Set it to equal 46.

// Declare a pointer and make it point to the variable my_int
// by using the address-of operator.
int *my_pointer = &my_int;

cout << my_int << endl; // Displays 46.

*my_pointer = 107; // Derefence and modify the variable.

cout << my_int << endl; // Displays 107.
cout << *my_pointer << endl; // Also 107.

Perhatikan dalam contoh ini bahwa kita tidak pernah mengalokasikan memori dengan operator "new". Kita mendeklarasikan variabel integer normal dan memanipulasinya melalui pointer.

Dalam contoh ini, kami mengilustrasikan penggunaan operator delete yang membatalkan alokasi memori heap, dan bagaimana kita dapat mengalokasikannya untuk struktur yang lebih kompleks. Kita akan membahas organisasi memori (tumpukan heap dan runtime) dalam pelajaran lain. Untuk saat ini, anggap saja heap ini sebagai penyimpanan memori gratis yang tersedia untuk menjalankan program.

int *ptr1; // Declare a pointer to int.
ptr1 = new int; // Reserve storage and point to it.

float *ptr2 = new float; // Do it all in one statement.

delete ptr1; // Free the storage.
delete ptr2;

Dalam contoh terakhir ini, kami menunjukkan cara pointer digunakan untuk meneruskan nilai dengan referensi ke fungsi. Ini adalah cara kita memodifikasi nilai variabel dalam sebuah fungsi.

// Passing parameters by reference.
#include <iostream>
using namespace std;

void Duplicate(int& a, int& b, int& c) {
  a *= 2;
  b *= 2;
  c *= 2;
}

int main() {
  int x = 1, y = 3, z = 7;
  Duplicate(x, y, z);
  // The following outputs: x=2, y=6, z=14.
  cout << "x="<< x << ", y="<< y << ", z="<< z;
  return 0;
}

Jika kita harus membiarkan tanda & tidak pada argumen dalam definisi Fungsi duplikat, kita akan meneruskan variabel "berdasarkan nilai", yaitu salinan dibuat dari nilai variabel. Setiap perubahan yang dibuat pada variabel dalam fungsi akan mengubah salinan. Mereka tidak memodifikasi variabel asli.

Jika variabel diteruskan melalui referensi, kita tidak meneruskan salinan nilainya, kita meneruskan alamat variabel ke fungsi. Setiap modifikasi yang kami lakukan pada variabel lokal sebenarnya akan mengubah variabel asli yang diteruskan. 

Jika Anda seorang {i>programmer<i} C, ini adalah sentuhan baru. Kita dapat melakukan hal yang sama di C dengan mendeklarasikan Duplicate() sebagai Duplicate(int *x), dalam hal ini x adalah pointer ke int, lalu memanggil Duplicate() dengan argumen &x (address-of x), dan menggunakan de-referensi x dalam Duplicate() (lihat di bawah). Namun, C++ menyediakan cara yang lebih sederhana untuk meneruskan nilai ke fungsi melalui referensi, meskipun cara "C" lama dalam melakukannya masih berfungsi.

void Duplicate(int *a, int *b, int *c) {
  *a *= 2;
  *b *= 2;
  *c *= 2;
}

int main() {
  int x = 1, y = 3, z = 7;
  Duplicate(&x, &y, &z);
  // The following outputs: x=2, y=6, z=14.
  cout << "x=" << x << ", y=" << y << ", z=" << z;
  return 0;
}

Perhatikan bahwa pada referensi C++, kita tidak perlu meneruskan alamat variabel, dan juga tidak perlu melakukan dereferensi variabel di dalam fungsi yang dipanggil.

Apa yang dihasilkan oleh program berikut? Gambar kenangan untuk mencari tahu.

void DoIt(int &foo, int goo);

int main() {
  int *foo, *goo;
  foo = new int;
  *foo = 1;
  goo = new int;
  *goo = 3;
  *foo = *goo + 3;
  foo = goo;
  *goo = 5;
  *foo = *goo + *foo;
  DoIt(*foo, *goo);
  cout << (*foo) << endl;
}

void DoIt(int &foo, int goo) {
  foo = goo + 3;
  goo = foo + 4;
  foo = goo + 3;
  goo = foo;
} 

Jalankan program untuk melihat apakah Anda mendapatkan jawaban yang benar.

Contoh #3: Meneruskan Nilai dengan Referensi

Tulis fungsi bernama speed() yang mengambil kecepatan kendaraan dan jumlah sebagai input. Fungsi ini menambahkan nilai tersebut pada kecepatan untuk mempercepat kendaraan. Parameter kecepatan harus diteruskan melalui referensi, dan jumlah berdasarkan nilai. Berikut adalah solusi kami.

Contoh #4: Class dan Objek

Pertimbangkan class berikut:

// time.cpp, Maggie Johnson
// Description: A simple time class.

#include <iostream>
using namespace std;

class Time {
 private:
  int hours_;
  int minutes_;
  int seconds_;
 public:
  void set(int h, int m, int s) {hours_ = h; minutes_ = m; seconds_ = s; return;}
  void increment();
  void display();
};

void Time::increment() {
  seconds_++;
  minutes_ += seconds_/60;
  hours_ += minutes_/60;
  seconds_ %= 60;
  minutes_ %= 60;
  hours_ %= 24;
  return;
}

void Time::display() {
  cout << (hours_ % 12 ? hours_ % 12:12) << ':'
       << (minutes_ < 10 ? "0" :"") << minutes_ << ':'
       << (seconds_ < 10 ? "0" :"") << seconds_
       << (hours_ < 12 ? " AM" : " PM") << endl;
}

int main() {
  Time timer;
  timer.set(23,59,58);
  for (int i = 0; i < 5; i++) {
    timer.increment();
    timer.display();
    cout << endl;
  }
}

Perhatikan bahwa variabel anggota class memiliki garis bawah di belakangnya. Ini dilakukan untuk membedakan antara variabel lokal dan variabel class.

Tambahkan metode pengurangan ke class ini. Berikut adalah solusi kami.

Keajaiban Sains: Ilmu Komputer

Latihan

Seperti di modul pertama, kami tidak memberikan solusi untuk latihan dan project.

Ingatlah Itu Program yang Bagus...

... diurai secara logis menjadi fungsi, di mana satu fungsi dapat melakukan satu tugas saja.

... memiliki program utama yang berbunyi seperti garis besar tentang apa yang akan dilakukan program tersebut.

... memiliki fungsi deskriptif, nama konstanta dan variabel.

... menggunakan konstanta untuk menghindari angka "ajaib" dalam program.

... memiliki antarmuka pengguna yang ramah.

Latihan Pemanasan

  • Latihan 1

    Bilangan bulat 36 memiliki sifat yang aneh: bilangan tersebut merupakan kuadrat sempurna, dan juga merupakan jumlah bilangan bulat antara 1 sampai 8. Angka berikutnya adalah 1225, yaitu 352, dan jumlah bilangan bulat antara 1 sampai 49. Temukan bilangan berikutnya yang merupakan kuadrat sempurna dan juga jumlah deret 1...n. Angka berikutnya ini mungkin lebih besar dari 32767. Anda dapat menggunakan fungsi library yang Anda ketahui, (atau rumus matematika) untuk membuat program berjalan lebih cepat. Anda juga dapat menulis program ini menggunakan for-loop untuk menentukan apakah suatu bilangan merupakan kuadrat sempurna atau hasil penjumlahan dari deret. (Catatan: bergantung pada mesin dan program Anda, perlu waktu agak lama untuk menemukan angka ini.)

  • Latihan 2

    Toko buku kampus Anda memerlukan bantuan dalam memperkirakan bisnisnya untuk tahun depan. Pengalaman menunjukkan bahwa penjualan sangat bergantung pada apakah buku diperlukan untuk kursus atau hanya opsional, dan apakah buku pernah digunakan di kelas sebelumnya. Buku pelajaran baru yang diperlukan akan laku 90% dari jumlah calon pendaftaran, tetapi jika sudah pernah digunakan di kelas sebelumnya, hanya 65% yang akan membeli. Demikian pula, 40% calon pendaftaran akan membeli buku pelajaran opsional baru, tetapi jika telah digunakan di kelas, sebelumnya hanya 20% yang akan membeli. (Perhatikan bahwa "bekas" di sini tidak berarti buku bekas.)

  • Menulis program yang menerima serangkaian buku sebagai input (hingga pengguna memasuki sentinel). Untuk setiap permintaan buku: kode buku, biaya salinan tunggal buku, jumlah buku yang saat ini dimiliki, pendaftaran kelas calon, dan data yang menunjukkan apakah buku tersebut diperlukan/opsional, baru/digunakan sebelumnya. Sebagai output, tampilkan semua informasi input di layar yang diformat dengan baik beserta berapa banyak buku yang harus diurutkan (jika ada, perhatikan bahwa hanya buku baru yang diurutkan), total biaya setiap pesanan.

    Kemudian, setelah semua input selesai, tampilkan total biaya semua pesanan buku, dan laba yang diharapkan jika toko membayar 80% dari harga jual. Karena kita belum membahas cara menangani set data yang besar yang masuk ke dalam suatu program (nantikan terus!), cukup proses satu buku dalam satu waktu dan tampilkan layar output buku tersebut. Kemudian, setelah pengguna selesai memasukkan semua data, program Anda akan menampilkan nilai total dan laba.

    Sebelum Anda mulai menulis kode, luangkan waktu untuk memikirkan desain program ini. Uraikan menjadi sekumpulan fungsi, dan buat fungsi main() yang dibaca seperti garis besar untuk solusi Anda atas masalah tersebut. Pastikan setiap fungsi melakukan satu tugas.

    Berikut adalah contoh output:

    Please enter the book code: 1221
     single copy price: 69.95
     number on hand: 30
     prospective enrollment: 150
     1 for reqd/0 for optional: 1
     1 for new/0 for used: 0
    ***************************************************
    Book: 1221
    Price: $69.95
    Inventory: 30
    Enrollment: 150
    
    This book is required and used.
    ***************************************************
    Need to order: 67
    Total Cost: $4686.65
    ***************************************************
    
    Enter 1 to do another book, 0 to stop. 0
    ***************************************************
    Total for all orders: $4686.65
    Profit: $937.33
    ***************************************************

Project Database

Dalam project ini, kami membuat program C++ yang berfungsi penuh dan mengimplementasikan sebuah aplikasi database sederhana.

Dengan program ini, kami dapat mengelola database komposer dan informasi yang relevan tentang mereka. Fitur program ini meliputi:

  • Kemampuan untuk menambahkan komposer baru
  • Kemampuan untuk memberi peringkat pada komposer (artinya, menunjukkan seberapa suka atau tidak suka pada musik sang komposer)
  • Kemampuan untuk melihat semua komposer dalam database
  • Kemampuan untuk melihat semua komposer berdasarkan peringkat

"Ada dua cara untuk membuat desain software: Salah satu caranya adalah membuatnya sesederhana mungkin sehingga jelas tidak akan ada kekurangan, dan cara lainnya adalah membuatnya begitu rumit sehingga tidak ada kekurangan yang jelas. Metode pertama jauh lebih sulit." - C.A.R. Hoare

Kebanyakan dari kita belajar mendesain dan menulis kode dengan pendekatan "prosedur". Pertanyaan utama untuk memulai adalah "Apa yang harus dilakukan program itu?". Kami menguraikan solusi atas suatu masalah menjadi beberapa tugas, yang masing-masingnya menyelesaikan sebagian masalah. Tugas ini dipetakan ke fungsi dalam program kami yang dipanggil secara berurutan dari main() atau dari fungsi lain. Pendekatan langkah demi langkah ini ideal untuk beberapa masalah yang perlu diselesaikan. Namun, program kita sering kali bukan hanya rangkaian tugas atau peristiwa linier.

Dengan pendekatan berorientasi objek (OO), kita mulai dengan pertanyaan "Apa objek dunia nyata yang saya modelkan?" Alih-alih membagi program menjadi tugas-tugas seperti yang dijelaskan di atas, kami membaginya menjadi beberapa model objek fisik. Objek fisik ini memiliki status yang ditentukan oleh serangkaian atribut, dan serangkaian perilaku atau tindakan yang dapat dilakukannya. Tindakan ini dapat mengubah status objek, atau memanggil tindakan objek lain. Premis dasarnya adalah bahwa objek "mengetahui" cara melakukan sesuatu dengan sendirinya. 

Dalam desain OO, kami mendefinisikan objek fisik dalam hal class dan objek; atribut serta perilaku. Umumnya ada banyak objek dalam program OO. Namun, banyak dari objek ini pada dasarnya sama. Pertimbangkan hal berikut.

Class adalah sekumpulan atribut dan perilaku umum untuk suatu objek, yang mungkin ada secara fisik di dunia nyata. Dalam ilustrasi di atas, kita memiliki class Apple. Semua apel, apa pun jenisnya, memiliki atribut warna dan rasa. Kami juga telah menentukan perilaku saat Apple menampilkan atributnya.

Dalam diagram ini, kita telah menentukan dua objek yang merupakan class Apple. Setiap objek memiliki atribut dan tindakan yang sama dengan class, tetapi objek menentukan atribut untuk jenis apel tertentu. Selain itu, tindakan Tampilan menampilkan atribut untuk objek tertentu tersebut, misalnya, "Hijau" dan "Asam".

Desain OO terdiri dari kumpulan class, data yang terkait dengan class ini, dan kumpulan tindakan yang dapat dilakukan class. Kita juga perlu mengidentifikasi cara class yang berbeda berinteraksi. Interaksi ini dapat dilakukan oleh objek dari suatu class yang memanggil tindakan objek dari class lain. Sebagai contoh, kita dapat memiliki class AppleOutputer yang menghasilkan warna dan rasa dari array objek Apple, dengan memanggil metode Display() dari setiap objek Apple.

Berikut langkah-langkah yang kita lakukan dalam melakukan desain OO:

  1. Identifikasi class dan tentukan secara umum apa yang disimpan oleh objek dari setiap class sebagai data dan apa yang dapat dilakukan objek tersebut.
  2. Menentukan elemen data setiap class
  3. Tentukan tindakan setiap class dan bagaimana beberapa tindakan dari satu class dapat diterapkan menggunakan tindakan class terkait lainnya.

Untuk sistem besar, langkah-langkah ini terjadi secara iteratif pada tingkat detail yang berbeda.

Untuk sistem database composer, kita memerlukan class Composer yang mengenkapsulasi semua data yang ingin disimpan di setiap composer. Objek class ini dapat mempromosikan atau mendemosikan dirinya sendiri (mengubah peringkatnya), dan menampilkan atributnya.

Kita juga memerlukan kumpulan objek Composer. Untuk itu, kami menetapkan class Database yang mengelola masing-masing record. Objek dari class ini dapat menambahkan atau mengambil objek Composer, dan menampilkan masing-masing objek dengan memanggil tindakan tampilan objek Composer.

Terakhir, kita memerlukan semacam antarmuka pengguna untuk menyediakan operasi interaktif pada database. Ini adalah class placeholder, yaitu kita benar-benar belum tahu seperti apa antarmuka pengguna nantinya, tetapi kita tahu bahwa kita akan memerlukannya. Mungkin berbasis grafis, atau berbasis teks. Untuk saat ini, kami menentukan placeholder yang dapat diisi nanti.

Setelah mengidentifikasi class untuk aplikasi database composer, langkah berikutnya adalah menentukan atribut dan tindakan untuk class tersebut. Dalam aplikasi yang lebih kompleks, kita akan menggunakan pensil dan kertas atau UML atau kartu CRC atau OOD untuk memetakan hierarki class dan cara objek berinteraksi.

Untuk database composer, kita menentukan class Composer yang berisi data relevan yang ingin disimpan di setiap composer. Class ini juga berisi metode untuk memanipulasi peringkat dan menampilkan data.

Class Database memerlukan semacam struktur untuk menyimpan objek Composer. Kita harus dapat menambahkan objek Composer baru ke struktur, serta mengambil objek Composer tertentu. Kita juga ingin menampilkan semua objek sesuai urutan entri atau peringkat.

Class Antarmuka Pengguna menerapkan antarmuka berdasarkan menu, dengan pengendali yang memanggil tindakan di class Database. 

Jika class mudah dipahami dan atribut serta tindakannya jelas, seperti dalam aplikasi composer, mendesain class akan relatif mudah. Namun, jika ada pertanyaan tentang bagaimana class terkait dan berinteraksi, sebaiknya gambarlah terlebih dahulu, lalu pelajari detailnya sebelum mulai membuat kode.

Setelah mendapatkan gambaran yang jelas tentang desain dan mengevaluasinya (lebih lanjut tentang hal ini dalam waktu dekat), kita akan menentukan antarmuka untuk setiap class. Pada tahap ini, kita tidak perlu mengkhawatirkan detail penerapan - cukup apa saja atribut dan tindakannya, serta bagian dari status dan tindakan class yang tersedia untuk class lain.

Pada C++, kita biasanya melakukan ini dengan mendefinisikan file header untuk setiap class. Class Composer memiliki anggota data pribadi untuk semua data yang ingin disimpan di composer. Kita memerlukan pengakses (metode “get”) dan mutator (metode “set”), serta tindakan utama untuk class tersebut.

// composer.h, Maggie Johnson
// Description: The class for a Composer record.
// The default ranking is 10 which is the lowest possible.
// Notice we use const in C++ instead of #define.
const int kDefaultRanking = 10;

class Composer {
 public:
  // Constructor
  Composer();
  // Here is the destructor which has the same name as the class
  // and is preceded by ~. It is called when an object is destroyed
  // either by deletion, or when the object is on the stack and
  // the method ends.
  ~Composer();

  // Accessors and Mutators
  void set_first_name(string in_first_name);
  string first_name();
  void set_last_name(string in_last_name);
  string last_name();
  void set_composer_yob(int in_composer_yob);
  int composer_yob();
  void set_composer_genre(string in_composer_genre);
  string composer_genre();
  void set_ranking(int in_ranking);
  int ranking();
  void set_fact(string in_fact);
  string fact();

  // Methods
  // This method increases a composer's rank by increment.
  void Promote(int increment);
  // This method decreases a composer's rank by decrement.
  void Demote(int decrement);
  // This method displays all the attributes of a composer.
  void Display();

 private:
  string first_name_;
  string last_name_;
  int composer_yob_; // year of birth
  string composer_genre_; // baroque, classical, romantic, etc.
  string fact_;
  int ranking_;
};

Class Database juga bersifat sederhana.

// database.h, Maggie Johnson
// Description: Class for a database of Composer records.
#include  <iostream>
#include "Composer.h"

// Our database holds 100 composers, and no more.
const int kMaxComposers = 100;

class Database {
 public:
  Database();
  ~Database();

  // Add a new composer using operations in the Composer class.
  // For convenience, we return a reference (pointer) to the new record.
  Composer& AddComposer(string in_first_name, string in_last_name,
                        string in_genre, int in_yob, string in_fact);
  // Search for a composer based on last name. Return a reference to the
  // found record.
  Composer& GetComposer(string in_last_name);
  // Display all composers in the database.
  void DisplayAll();
  // Sort database records by rank and then display all.
  void DisplayByRank();

 private:
  // Store the individual records in an array.
  Composer composers_[kMaxComposers];
  // Track the next slot in the array to place a new record.
  int next_slot_;
};

Perhatikan cara kami mengenkapsulasi data khusus komposer dengan cermat dalam class terpisah. Kita dapat menempatkan struct atau class di class Database untuk mewakili data Composer, dan mengaksesnya langsung di sana. Namun, itu akan berupa "dalam objektifikasi", yaitu, kita tidak membuat model dengan objek sebanyak yang kita bisa.

Anda akan melihat ketika mulai mengerjakan implementasi class Composer dan Database, bahwa akan jauh lebih mudah jika memiliki class Composer yang terpisah. Secara khusus, memiliki operasi atomik terpisah pada objek Composer sangat menyederhanakan implementasi metode Display() di class Database.

Tentu saja, ada juga yang disebut "objektifikasi berlebih" saat kita mencoba dan menjadikan semuanya sebagai class, atau kita memiliki lebih banyak class daripada yang dibutuhkan. Menemukan keseimbangan yang tepat memerlukan latihan, dan Anda akan mendapati bahwa setiap programmer akan memiliki pendapat yang berbeda. 

Menentukan apakah Anda melakukan objek yang berlebihan atau kurang sering dapat diurutkan dengan membuat diagram class Anda secara hati-hati. Seperti disebutkan sebelumnya, penting untuk mengerjakan desain class sebelum memulai coding dan hal ini dapat membantu menganalisis pendekatan Anda. Notasi umum yang digunakan untuk tujuan ini adalah UML (Unified Modeling Language) Setelah memiliki class yang ditentukan untuk objek Composer dan Database, kita memerlukan antarmuka yang memungkinkan pengguna berinteraksi dengan database. Menu yang sederhana akan membantu:

Composer Database
---------------------------------------------
1) Add a new composer
2) Retrieve a composer's data
3) Promote/demote a composer's rank
4) List all composers
5) List all composers by rank
0) Quit

Kita bisa menerapkan antarmuka pengguna sebagai class, atau sebagai program prosedural. Tidak semua yang ada dalam program C++ harus berupa class. Bahkan, jika pemrosesannya berurutan atau berorientasi pada tugas, seperti dalam program menu ini, tidak masalah untuk menerapkannya secara prosedural. Penting untuk menerapkannya sedemikian rupa sehingga tetap menjadi "placeholder", yaitu, jika kita ingin membuat antarmuka pengguna grafis (GUI) pada waktu tertentu, kita tidak perlu mengubah apa pun di sistem kecuali antarmuka pengguna.

Hal terakhir yang kita perlukan untuk menyelesaikan aplikasi adalah program untuk menguji class. Untuk class Composer, kita ingin program main() yang mengambil input, mengisi objek composer, lalu menampilkannya untuk memastikan class tersebut berfungsi dengan benar. Kita juga ingin memanggil semua metode class Composer.

// test_composer.cpp, Maggie Johnson
//
// This program tests the Composer class.

#include <iostream>
#include "Composer.h"
using namespace std;

int main()
{
  cout << endl << "Testing the Composer class." << endl << endl;

  Composer composer;

  composer.set_first_name("Ludwig van");
  composer.set_last_name("Beethoven");
  composer.set_composer_yob(1770);
  composer.set_composer_genre("Romantic");
  composer.set_fact("Beethoven was completely deaf during the latter part of "
    "his life - he never heard a performance of his 9th symphony.");
  composer.Promote(2);
  composer.Demote(1);
  composer.Display();
}

Kita memerlukan program pengujian serupa untuk class Database.

// test_database.cpp, Maggie Johnson
//
// Description: Test driver for a database of Composer records.
#include <iostream>
#include "Database.h"
using namespace std;

int main() {
  Database myDB;

  // Remember that AddComposer returns a reference to the new record.
  Composer& comp1 = myDB.AddComposer("Ludwig van", "Beethoven", "Romantic", 1770,
    "Beethoven was completely deaf during the latter part of his life - he never "
    "heard a performance of his 9th symphony.");
  comp1.Promote(7);

  Composer& comp2 = myDB.AddComposer("Johann Sebastian", "Bach", "Baroque", 1685,
    "Bach had 20 children, several of whom became famous musicians as well.");
  comp2.Promote(5);

  Composer& comp3 = myDB.AddComposer("Wolfgang Amadeus", "Mozart", "Classical", 1756,
    "Mozart feared for his life during his last year - there is some evidence "
    "that he was poisoned.");
  comp3.Promote(2);

  cout << endl << "all Composers: " << endl << endl;
  myDB.DisplayAll();
}

Perhatikan bahwa program pengujian sederhana ini adalah langkah pertama yang baik, tetapi program ini mengharuskan kita untuk memeriksa output secara manual untuk memastikan program berfungsi dengan benar. Dengan semakin besarnya sistem, pemeriksaan output secara manual dengan cepat menjadi tidak praktis. Dalam pelajaran berikutnya, kami akan memperkenalkan program pengujian pemeriksaan mandiri dalam bentuk pengujian unit.

Desain untuk aplikasi kita sekarang sudah selesai. Langkah berikutnya adalah mengimplementasikan file .cpp untuk class dan antarmuka pengguna.Untuk memulai, salin/tempel file .h dan uji kode driver di atas ke dalam file, lalu kompilasikan file tersebut.Gunakan driver pengujian untuk menguji class Anda. Kemudian, implementasikan antarmuka berikut:

Composer Database
---------------------------------------------
1) Add a new composer
2) Retrieve a composer's data
3) Promote/demote a composer's rank
4) List all composers
5) List all composers by rank
0) Quit

Gunakan metode yang Anda tentukan di class Database untuk menerapkan antarmuka pengguna. Buat metode Anda tahan terhadap error. Misalnya, peringkat harus selalu dalam rentang 1-10. Jangan izinkan siapa pun menambahkan 101 composer, kecuali jika Anda berencana mengubah struktur data di class Database.

Ingat - semua kode Anda harus mengikuti konvensi coding kami, yang diulang di sini untuk memudahkan Anda:

  • Setiap program yang kami tulis dimulai dengan komentar header, yang memberikan nama penulis, informasi kontak, deskripsi singkat, dan penggunaannya (jika relevan). Setiap fungsi/metode dimulai dengan komentar tentang operasi dan penggunaan.
  • Kami menambahkan komentar penjelasan menggunakan kalimat lengkap, setiap kali kode tidak mendokumentasikan dirinya sendiri, misalnya, jika pemrosesannya rumit, tidak jelas, menarik, atau penting.
  • Selalu gunakan nama deskriptif: variabel adalah kata berhuruf kecil yang dipisahkan oleh _, seperti dalam my_variable. Nama fungsi/metode menggunakan huruf besar untuk menandai kata, seperti dalam MyEx naikFunction(). Konstanta dimulai dengan "k" dan menggunakan huruf besar untuk menandai kata, seperti dalam kDaysInWeek.
  • Indentasi adalah kelipatan dua. Tingkat pertama adalah dua spasi; jika perlu indentasi lebih lanjut, kami menggunakan empat spasi, enam spasi, dst.

Selamat datang di Dunia Nyata!

Dalam modul ini, kami memperkenalkan dua alat yang sangat penting yang digunakan di sebagian besar organisasi software engineering. Yang pertama adalah alat build, dan yang kedua adalah sistem manajemen konfigurasi. Kedua alat ini sangat penting dalam software engineering industri, tempat banyak engineer sering bekerja di satu sistem besar. Alat-alat ini membantu mengoordinasikan dan mengontrol perubahan pada code base, serta memberikan cara yang efisien untuk mengompilasi dan menautkan sistem dari banyak file program dan header.

Makefile

Proses pembuatan program biasanya dikelola dengan alat build, yang mengompilasi dan menautkan file yang diperlukan, dalam urutan yang benar. Cukup sering, file C++ memiliki dependensi. Misalnya, fungsi yang dipanggil dalam satu program berada di program lain. Atau, mungkin file header diperlukan oleh beberapa file .cpp yang berbeda. Alat build mencari tahu urutan kompilasi yang benar dari dependensi ini. Kode ini juga hanya akan mengompilasi file yang telah berubah sejak build terakhir. Hal ini dapat menghemat banyak waktu dalam sistem yang terdiri dari beberapa ratusan atau ribuan file.

Alat build open source yang disebut make biasanya digunakan. Untuk mempelajarinya, baca artikel ini. Lihat apakah Anda dapat membuat grafik dependensi untuk aplikasi Database Composer, lalu menerjemahkannya menjadi makefile.Berikut adalah solusi kami.

Sistem Pengelolaan Konfigurasi

Alat kedua yang digunakan dalam software engineering industri adalah Configuration Management (CM). Ini digunakan untuk mengelola perubahan. Katakanlah Bob dan Susan adalah penulis teknologi, yang sedang mengerjakan update manual teknis. Selama rapat, manajernya memberi mereka bagian yang sama dalam dokumen yang sama untuk diperbarui.

Panduan teknis disimpan di komputer yang dapat diakses oleh Bob dan Susan. Tanpa alat atau proses CM, sejumlah masalah dapat muncul. Salah satu skenario yang mungkin terjadi adalah komputer yang menyimpan dokumen mungkin telah disiapkan sehingga Bob dan Susan tidak dapat mengerjakan manual secara bersamaan. Hal ini akan memperlambat kerja mereka secara signifikan.

Situasi yang lebih berbahaya akan muncul jika komputer penyimpanan mengizinkan dokumen dibuka oleh Bob dan Susan secara bersamaan. Inilah yang mungkin terjadi:

  1. Bob membuka dokumen di komputernya dan mengerjakan bagiannya.
  2. Susan membuka dokumen tersebut di komputer dan mengerjakan bagiannya.
  3. Bobi menyelesaikan perubahannya dan menyimpan dokumen itu di komputer penyimpanan.
  4. Susan menyelesaikan perubahannya dan menyimpan dokumen itu di komputer penyimpanan.

Ilustrasi ini menunjukkan masalah yang dapat terjadi jika tidak ada kontrol pada satu salinan panduan teknis. Saat Susan menyimpan perubahannya, dia akan menimpa perubahan yang dibuat oleh Bob.

Inilah situasi yang dapat dikontrol oleh sistem CM. Dengan sistem CM, Bob dan Susan "memeriksa" salinan panduan teknis mereka sendiri dan mengerjakannya. Saat Bob memeriksa kembali perubahannya, sistem mengetahui bahwa Susan telah memeriksa salinannya sendiri. Saat Susan memeriksa salinannya, sistem akan menganalisis perubahan yang dibuat Bob dan Susan, dan membuat versi baru yang menggabungkan kedua kumpulan perubahan tersebut.

Sistem CM memiliki sejumlah fitur selain mengelola perubahan serentak seperti yang dijelaskan di atas. Banyak sistem menyimpan arsip semua versi dokumen, sejak pertama kali dibuat. Dalam kasus panduan teknis, hal ini dapat sangat membantu jika pengguna memiliki panduan versi lama dan mengajukan pertanyaan kepada penulis teknologi. Sistem CM akan memungkinkan penulis teknologi mengakses versi lama dan dapat melihat apa yang dilihat pengguna.

Sistem CM sangat berguna dalam mengontrol perubahan yang dilakukan pada perangkat lunak. Sistem semacam ini disebut sistem Software Configuration Management (SCM). Jika Anda mempertimbangkan banyaknya file kode sumber individual dalam organisasi software engineering yang besar dan banyaknya engineer yang harus melakukan perubahan pada file tersebut, jelas bahwa sistem SCM sangatlah penting.

Manajemen Konfigurasi Perangkat Lunak

Sistem SCM didasarkan pada sebuah ide sederhana: salinan definitif file Anda disimpan di repositori pusat. Orang memeriksa salinan file dari repositori, mengerjakan salinan tersebut, lalu memeriksanya kembali saat selesai. Sistem SCM mengelola dan melacak revisi oleh banyak orang terhadap satu set master. 

Semua sistem SCM menyediakan fitur penting berikut:

  • Pengelolaan Konkurensi
  • Pembuatan versi
  • Sinkronisasi

Mari kita lihat masing-masing fitur ini secara lebih mendetail.

Pengelolaan Konkurensi

Konkurensi mengacu pada pengeditan file secara bersamaan yang dilakukan oleh lebih dari satu orang. Dengan repositori besar, kami ingin orang dapat melakukannya, tetapi hal ini dapat menimbulkan beberapa masalah.

Pertimbangkan contoh sederhana dalam domain engineer: Misalkan kita mengizinkan engineer untuk memodifikasi file yang sama secara bersamaan di repositori pusat kode sumber. Client1 dan Client2 perlu membuat perubahan pada file secara bersamaan:

  1. Klien1 membuka bar.cpp.
  2. Client2 membuka bar.cpp.
  3. Client1 mengubah file dan menyimpannya.
  4. {i>Client2<i} mengubah file dan menyimpannya sehingga menimpa perubahan {i>Client1<i}.

Tentu saja, kami tidak ingin hal ini terjadi. Meskipun kami mengendalikan situasi dengan meminta kedua engineer mengerjakan salinan terpisah, bukan langsung pada set master (seperti dalam ilustrasi di bawah), salinan tersebut harus direkonsiliasi. Sebagian besar sistem SCM mengatasi masalah ini dengan mengizinkan beberapa engineer memeriksa file ("sinkronisasi" atau "update") dan membuat perubahan sesuai kebutuhan. Sistem SCM kemudian menjalankan algoritma untuk menggabungkan perubahan saat file diperiksa kembali ("submit" atau "commit") ke repositori.

Algoritma ini dapat bersifat sederhana (meminta engineer untuk menyelesaikan perubahan yang bertentangan) atau tidak terlalu sederhana (menentukan cara menggabungkan perubahan yang bertentangan dengan cerdas dan hanya bertanya kepada engineer jika sistem benar-benar bermasalah). 

Pembuatan versi

Pembuatan versi mengacu pada pelacakan revisi file yang memungkinkan pembuatan ulang (atau roll back ke) versi file sebelumnya. Hal ini dilakukan dengan membuat salinan arsip setiap file saat diperiksa ke dalam repositori, atau dengan menyimpan setiap perubahan yang dibuat pada file. Kami dapat menggunakan arsip atau mengubah informasi untuk membuat versi sebelumnya kapan saja. Sistem pembuatan versi juga dapat membuat laporan log tentang siapa yang memeriksa perubahan, kapan mereka melakukan check in, dan apa perubahannya.

Sinkronisasi

Dengan beberapa sistem SCM, setiap file diperiksa masuk dan keluar dari repositori. Sistem yang lebih canggih memungkinkan Anda memeriksa lebih dari satu file sekaligus. Engineer memeriksa salinan repositori (atau bagiannya) yang lengkap, dan mengerjakan file sesuai kebutuhan. Selanjutnya, mereka meng-commit perubahan kembali ke repositori master secara berkala, dan memperbarui salinan pribadi mereka agar selalu mengetahui perubahan yang dibuat oleh orang lain. Proses ini disebut sinkronisasi atau pembaruan.

Subversi

Subversion (SVN) adalah sistem kontrol versi open source. SDK ini memiliki semua fitur yang dijelaskan di atas.

SVN menggunakan metodologi yang sederhana saat konflik terjadi. Konflik terjadi saat dua engineer atau lebih membuat perubahan yang berbeda pada area code base yang sama, lalu mengirimkan perubahan tersebut. SVN hanya memberi tahu engineer bahwa ada konflik - engineerlah yang akan menyelesaikannya.

Kami akan menggunakan SVN di sepanjang kursus ini untuk membantu Anda memahami manajemen konfigurasi. Sistem seperti ini sangat umum digunakan di industri.

Langkah pertama adalah menginstal SVN di sistem Anda. Klik di sini untuk mendapatkan petunjuk. Temukan sistem operasi Anda dan unduh biner yang sesuai.

Beberapa Terminologi SVN

  • Revisi: Perubahan dalam file atau kumpulan file. Revisi adalah salah satu "snapshot" dalam project yang terus berubah.
  • Repositori: Salinan master tempat SVN menyimpan histori revisi lengkap project. Setiap project memiliki satu repositori.
  • {i>Working Copy<i}: Salinan di mana seorang insinyur membuat perubahan pada sebuah proyek. Ada banyak salinan tugas dari suatu project yang masing-masing dimiliki oleh seorang engineer.
  • Check Out: Untuk meminta salinan yang berfungsi dari repositori. Salinan yang berfungsi sama dengan status project saat diperiksa.
  • Commit: Untuk mengirim perubahan dari salinan kerja Anda ke repositori pusat. Juga dikenal sebagai check-in atau kirim.
  • Update: Untuk memindahkan perubahan orang lain dari repositori ke salinan kerja Anda, atau untuk menunjukkan apakah salinan kerja Anda memiliki perubahan yang belum di-commit. Ini sama dengan sinkronisasi, seperti yang dijelaskan di atas. Jadi, update/sync membuat salinan kerja Anda selalu diperbarui dengan salinan repositori.
  • Konflik: Situasi ketika dua engineer mencoba meng-commit perubahan pada area yang sama dalam sebuah file. SVN menunjukkan konflik, tetapi engineer harus menyelesaikannya.
  • Pesan log: Komentar yang dilampirkan ke revisi saat Anda melakukan commit untuk komentar tersebut, yang akan mendeskripsikan perubahan Anda. Log tersebut memberikan ringkasan tentang apa yang terjadi dalam sebuah project.

Setelah SVN terinstal, Anda akan menjalankan beberapa perintah dasar. Hal pertama yang harus dilakukan adalah menyiapkan repositori pada direktori yang ditentukan. Berikut ini perintahnya:

$ svnadmin create /usr/local/svn/newrepos
$ svn import mytree file:///usr/local/svn/newrepos/project -m "Initial import"
Adding         mytree/foo.c
Adding         mytree/bar.c
Adding         mytree/subdir
Adding         mytree/subdir/foobar.h

Committed revision 1.

Perintah import menyalin konten direktori mytree ke dalam project direktori di repositori. Kita dapat melihat direktori di repositori dengan perintah list

$ svn list file:///usr/local/svn/newrepos/project
bar.c
foo.c
subdir/

Impor tidak membuat salinan yang berfungsi. Untuk melakukannya, Anda harus menggunakan perintah svn checkout. Ini akan membuat salinan hierarki direktori yang berfungsi. Mari kita lakukan sekarang:

$ svn checkout file:///usr/local/svn/newrepos/project
A    foo.c
A    bar.c
A    subdir
A    subdir/foobar.h
…
Checked out revision 215.

Setelah memiliki salinan yang berfungsi, Anda dapat membuat perubahan pada file dan direktori di sana. Salinan kerja Anda sama seperti kumpulan file dan direktori lainnya - Anda dapat menambahkan yang baru atau mengeditnya, memindahkannya, bahkan Anda dapat menghapus seluruh salinan yang sudah dikerjakan. Perhatikan bahwa jika Anda menyalin dan memindahkan file dalam salinan kerja, penting untuk menggunakan svn copy dan svn move, bukan perintah sistem operasi Anda. Untuk menambahkan file baru, gunakan svn add dan untuk menghapus file, gunakan svn delete. Jika Anda ingin mengedit, cukup buka file menggunakan editor dan edit.

Ada beberapa nama direktori standar yang sering digunakan dengan {i>Subversion<i}. Direktori "trunk" memegang jalur utama pengembangan untuk project Anda. Direktori "cabang" berisi versi cabang apa pun yang mungkin sedang Anda kerjakan.

$ svn list file:///usr/local/svn/repos
/trunk
/branches

Jadi, katakanlah Anda telah membuat semua perubahan yang diperlukan pada salinan yang berfungsi dan Anda ingin menyinkronkannya dengan repositori. Jika ada banyak engineer lain yang bekerja di area repositori ini, pastikan salinan kerja Anda selalu aktual. Anda dapat menggunakan perintah svn status untuk melihat perubahan yang telah dibuat.

A       subdir/new.h      # file is scheduled for addition
D       subdir/old.c        # file is scheduled for deletion
M       bar.c                  # the content in bar.c has local modifications

Perhatikan bahwa ada banyak penanda pada perintah status untuk mengontrol {i>output<i} ini. Jika Anda ingin melihat perubahan tertentu pada file yang dimodifikasi, gunakan diff svn.

$ svn diff bar.c
Index: bar.c
===================================================================
--- bar.c	(revision 5)
+++ bar.c	(working copy)
## -1,18 +1,19 ##
+#include
+#include

 int main(void) {
-  int temp_var;
+ int new_var;
...

Terakhir, untuk memperbarui salinan kerja dari repositori, gunakan perintah svn update.

$ svn update
U  foo.c
U  bar.c
G  subdir/foobar.h
C  subdir/new.h
Updated to revision 2.

Ini adalah salah satu tempat di mana konflik mungkin terjadi. Pada output di atas, huruf "U" menunjukkan tidak ada perubahan yang dilakukan pada versi repositori file ini dan update telah dilakukan. "G" berarti penggabungan terjadi. Versi repositori telah diubah, tetapi perubahan tidak bertentangan dengan versi Anda. Huruf "C" menunjukkan konflik. Artinya, perubahan dari repositori tumpang-tindih dengan perubahan Anda, dan sekarang Anda harus memilih di antaranya.

Untuk setiap file yang memiliki konflik, Subversion menempatkan tiga file dalam salinan kerja Anda:

  • file.mine: Ini adalah file Anda seperti yang ada pada salinan kerja sebelum Anda memperbarui salinan kerja.
  • file.rOLDREV: Ini adalah file yang Anda check out dari repositori sebelum melakukan perubahan.
  • file.rNEWREV: File ini adalah versi saat ini dalam repositori.

Anda dapat melakukan salah satu dari tiga hal untuk menyelesaikan konflik:

  • Periksa file dan lakukan penggabungan secara manual.
  • Salin salah satu file sementara yang dibuat oleh SVN ke versi salinan kerja Anda.
  • Jalankan svnRevert untuk menghapus semua perubahan.

Setelah konflik tersebut diselesaikan, Anda memberi tahu SVN dengan menjalankan svnresolve. Tindakan ini akan menghapus tiga file sementara dan SVN tidak lagi melihat file dalam status konflik.

Hal terakhir yang harus dilakukan adalah meng-commit versi final Anda ke repositori. Ini dilakukan dengan perintah svn commit. Saat meng-commit perubahan, Anda harus menyediakan pesan log, yang menjelaskan perubahan tersebut. Pesan log ini dilampirkan pada revisi yang Anda buat.

svn commit -m "Update files to include new headers."  

Ada banyak hal yang perlu dipelajari tentang SVN, serta bagaimana SVN dapat mendukung project software engineering besar. Banyak resource yang tersedia di web - cukup telusuri "Subversion" di Google.

Sebagai latihan, buat repositori untuk sistem Database Composer Anda, lalu impor semua file Anda. Kemudian, lihat salinan yang masih berfungsi dan lakukan perintah yang dijelaskan di atas.

Referensi

Buku Subversi Online

Artikel Wikipedia tentang SVN

Situs subversi

Aplikasi: Studi dalam Anatomi

Lihat eSkeletons dari The University of Texas di Austin