Các bước tiếp theo

Giới thiệu về lập trình và C++

Hướng dẫn trực tuyến này tiếp tục với các khái niệm nâng cao hơn – vui lòng đọc Phần III. Trong học phần này, chúng ta sẽ tập trung vào việc sử dụng con trỏ và bắt đầu sử dụng các đối tượng.

Tìm hiểu qua ví dụ 2

Trong học phần này, chúng ta sẽ tập trung vào việc thực hành thêm về quá trình phân ly, hiểu về con trỏ, cũng như bắt đầu với các đối tượng và lớp. Hãy xem các ví dụ sau. Tự viết chương trình khi được yêu cầu hoặc thực hiện thử nghiệm. Chúng ta chưa thể nhấn mạnh đủ rằng chìa khoá để trở thành một lập trình viên giỏi chính là thực hành, thực hành và luyện tập!

Ví dụ 1: Thực hành phân ly khác

Hãy xem xét kết quả sau đây từ một trò chơi đơn giản:

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.

Quan sát đầu tiên là văn bản giới thiệu được hiển thị một lần cho mỗi lần thực thi chương trình. Chúng ta cần một trình tạo số ngẫu nhiên để xác định khoảng cách của đối phương cho mỗi vòng. Chúng ta cần một cơ chế để nhận thông tin về góc từ người chơi và rõ ràng là cơ chế này nằm trong một cấu trúc vòng lặp vì nó lặp lại cho đến khi chúng ta tấn công kẻ thù. Chúng ta cũng cần một hàm để tính khoảng cách và góc. Cuối cùng, chúng ta phải theo dõi số lần bắn vào kẻ thù, cũng như số lượng kẻ thù mà chúng ta đã phải bắn trong quá trình thực thi chương trình. Sau đây là một số đề xuất có thể dùng cho chương trình chính.

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;

Quy trình Fire xử lý việc chơi trò chơi. Trong hàm đó, chúng ta gọi một trình tạo số ngẫu nhiên để lấy khoảng cách của kẻ thù, sau đó thiết lập vòng lặp để nhận thông tin đầu vào của người chơi và tính toán xem người chơi có bắn trúng kẻ thù hay không. Điều kiện bảo vệ là mức độ mà chúng ta đã chạm trán kẻ thù.

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);

Do các lệnh gọi đến cos() và sin(), bạn sẽ cần đưa vào hàm toán học.h. Hãy thử viết chương trình này – bạn nên thực hành phân tích vấn đề và đánh giá tốt về C++ cơ bản. Hãy nhớ chỉ thực hiện một tác vụ trong mỗi hàm. Đây là chương trình phức tạp nhất mà chúng tôi viết từ trước đến nay, nên có thể bạn sẽ cần chút thời gian để thực hiện.Đây là giải pháp của chúng tôi. 

Ví dụ 2: Thực hành với con trỏ

Có 4 điều cần nhớ khi làm việc với con trỏ:
  1. Con trỏ là các biến chứa địa chỉ bộ nhớ. Khi một chương trình đang thực thi, tất cả biến được lưu trữ trong bộ nhớ, mỗi biến tại một địa chỉ hoặc vị trí duy nhất riêng. Con trỏ là một loại biến đặc biệt chứa địa chỉ bộ nhớ thay vì giá trị dữ liệu. Cũng giống như việc dữ liệu được sửa đổi khi sử dụng biến thông thường, giá trị của địa chỉ lưu trữ trong con trỏ cũng được sửa đổi khi biến con trỏ bị chỉnh sửa. Sau đây là một ví dụ:
    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. Chúng ta thường nói rằng một con trỏ "trỏ" đến vị trí mà nó đang lưu trữ ("con trỏ"). Vì vậy, trong ví dụ trên, intptr trỏ đến pointee 5.

    Hãy lưu ý việc sử dụng toán tử "new" (mới) để phân bổ bộ nhớ cho con trỏ số nguyên. Đây là việc chúng ta phải thực hiện trước khi cố gắng truy cập vào điểm nhận điểm.

    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.
          

    Toán tử * dùng để tham chiếu trong C. Một trong những lỗi phổ biến nhất mà các lập trình viên C/C++ mắc phải khi làm việc với con trỏ là quên khởi chạy con trỏ. Đôi khi, việc này có thể gây ra sự cố thời gian chạy vì chúng ta đang truy cập vào một vị trí trong bộ nhớ chứa dữ liệu không xác định. Nếu cố gắng sửa đổi dữ liệu này, chúng tôi có thể gây ra tình trạng hỏng bộ nhớ nhẹ, từ đó trở thành lỗi khó theo dõi. 

  3. Việc gán con trỏ giữa hai con trỏ khiến chúng trỏ đến cùng một con trỏ. Vì vậy, bài tập y = x; khiến y trỏ tới cùng một điểm trỏ với x. Hoạt động chỉ định con trỏ không chạm vào điểm trỏ. Thao tác này chỉ thay đổi một con trỏ để có cùng vị trí với một con trỏ khác. Sau khi chỉ định con trỏ, hai con trỏ sẽ "dùng chung" con trỏ. 
  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
    }
      

Dưới đây là dấu vết của mã này:

1. Phân bổ hai con trỏ x và y. Việc phân bổ con trỏ không phân bổ bất kỳ điểm nào.
2. Phân bổ một người bị trỏ và đặt x để trỏ đến đối tượng đó.
3. Tham chiếu x để lưu trữ 42 trong con trỏ. Đây là ví dụ cơ bản về thao tác huỷ tham chiếu. Bắt đầu tại x, theo mũi tên để truy cập vào điểm trỏ.
4. Hãy thử tham chiếu y để lưu trữ 13 trong con trỏ. Sự cố này xảy ra do bạn không có điểm trỏ – nó chưa bao giờ được chỉ định.
5. Gán y = x; để y trỏ tới pointee của x. Bây giờ, x và y trỏ tới cùng một người trỏ – hai người này đang "chia sẻ".
6. Hãy thử tham chiếu y để lưu trữ 13 trong con trỏ. Lần này hiệu quả hoạt động vì bài tập trước đã cho bạn một điểm phát sóng.

Như bạn có thể thấy, hình ảnh rất hữu ích trong việc hiểu rõ cách sử dụng con trỏ. Sau đây là một ví dụ khác.

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.

Lưu ý trong ví dụ này là chúng ta chưa bao giờ phân bổ bộ nhớ bằng toán tử "new". Chúng ta đã khai báo một biến số nguyên thông thường và điều khiển biến đó bằng con trỏ.

Trong ví dụ này, chúng tôi minh hoạ việc sử dụng toán tử xoá để huỷ phân bổ bộ nhớ vùng nhớ khối xếp, cũng như cách chúng ta có thể phân bổ cho các cấu trúc phức tạp hơn. Chúng ta sẽ đề cập đến cách tổ chức bộ nhớ (vùng nhớ khối xếp và ngăn xếp thời gian chạy) trong một bài học khác. Hiện tại, hãy xem vùng nhớ khối xếp là một nơi lưu trữ bộ nhớ miễn phí có sẵn để chạy các chương trình.

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;

Trong ví dụ cuối cùng này, chúng tôi cho thấy cách dùng con trỏ để truyền giá trị bằng cách tham chiếu đến một hàm. Đây là cách chúng ta sửa đổi giá trị của các biến trong một hàm.

// 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;
}

Nếu không tắt các đối số trong định nghĩa Hàm trùng lặp, chúng ta sẽ truyền các biến "theo giá trị", tức là một bản sao được tạo từ giá trị của biến. Mọi thay đổi đối với biến trong hàm sẽ sửa đổi bản sao. Chúng không sửa đổi biến gốc.

Khi một biến được chuyển qua tham chiếu, chúng ta không truyền bản sao giá trị của biến đó mà là chúng ta đang truyền địa chỉ của biến vào hàm. Mọi hoạt động sửa đổi mà chúng tôi thực hiện đối với biến cục bộ trên thực tế sẽ sửa đổi biến gốc đã truyền vào. 

Nếu bạn là lập trình viên C, đây sẽ là một bước ngoặt mới. Chúng ta có thể làm tương tự trong C bằng cách khai báo Duplicate() dưới dạng Duplicate(int *x), trong trường hợp đó x là con trỏ đến một số nguyên, sau đó gọi Duplicate() với đối số &x (địa chỉ của x) và sử dụng tính năng loại bỏ tham chiếu của x trong Duplicate() (xem bên dưới). Nhưng C++ cung cấp cách thức đơn giản hơn để truyền giá trị đến các hàm bằng cách tham chiếu, mặc dù cách "C" cũ để thực hiện việc này vẫn hoạt động.

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;
}

Lưu ý với các tham chiếu C++, chúng ta không cần truyền địa chỉ của biến và cũng không cần huỷ tham chiếu biến bên trong hàm được gọi.

Chương trình sau đây tạo ra gì? Vẽ hình ảnh về kỷ niệm để tìm hiểu.

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;
} 

Chạy chương trình để xem bạn có trả lời đúng hay không.

Ví dụ 3: Chuyển giá trị theo tham chiếu

Viết một hàm có tên là tăng tốc(), hàm này lấy tốc độ của xe và một số lượng đầu vào. Hàm này thêm tốc độ vào tốc độ để tăng tốc xe. Tham số tốc độ phải được chuyển theo tham chiếu và theo số lượng theo giá trị. Đây là giải pháp của chúng tôi.

Ví dụ 4: Lớp và Đối tượng

Hãy xem xét lớp sau:

// 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;
  }
}

Lưu ý rằng các biến thành phần của lớp có dấu gạch dưới. Mục đích của việc này là để phân biệt giữa biến cục bộ và biến lớp.

Thêm phương thức giảm vào lớp này. Đây là giải pháp của chúng tôi.

Những điều kỳ diệu của khoa học: Khoa học máy tính

Bài tập

Như trong học phần đầu tiên của khoá học này, chúng tôi không cung cấp giải pháp cho các bài tập thể dục và dự án.

Hãy nhớ rằng một chương trình hay...

... được phân rã theo cách hợp lý thành các hàm, trong đó một hàm bất kỳ thực hiện một và chỉ một tác vụ.

... có một chương trình chính giống như một bản phác thảo về những gì chương trình sẽ làm.

... có tên hàm, hằng số và biến mô tả.

... sử dụng các hằng số để tránh mọi số "ma thuật" trong chương trình.

... có giao diện người dùng thân thiện.

Bài tập khởi động

  • Bài tập 1

    Số nguyên 36 có một đặc điểm đặc biệt: đây là số chính phương và cũng là tổng các số nguyên từ 1 đến 8. Số tiếp theo là 1225, tức là 352 và là tổng của các số nguyên từ 1 đến 49. Tìm số tiếp theo là số chính phương và cũng là tổng của chuỗi 1...n. Số tiếp theo này có thể lớn hơn 32767. Bạn có thể sử dụng các hàm thư viện mà bạn biết (hoặc công thức toán học) để chương trình chạy nhanh hơn. Bạn cũng có thể viết chương trình này bằng cách dùng vòng lặp để xác định xem một số có phải là số chính phương hay tổng của một chuỗi hay không. (Lưu ý: tuỳ thuộc vào máy và chương trình của bạn, có thể mất một chút thời gian để tìm thấy số này.)

  • Bài tập 2

    Cửa hàng sách đại học của bạn cần bạn giúp ước tính hoạt động kinh doanh cho năm tới. Kinh nghiệm cho thấy rằng doanh số bán hàng phụ thuộc rất lớn vào việc một cuốn sách là bắt buộc phải có cho một khoá học hay không, và việc cuốn sách đó có được sử dụng trong lớp học này trước đó hay không. Một cuốn sách giáo khoa mới, bắt buộc sẽ bán được cho 90% học sinh tiềm năng đăng ký học. Tuy nhiên, nếu trước đó bạn đã sử dụng cuốn sách giáo khoa đó trong lớp học thì chỉ có 65% mua. Tương tự, 40% học viên tiềm năng sẽ mua sách giáo khoa mới và không bắt buộc. Tuy nhiên, nếu sách đã được sử dụng trong lớp trước đó thì chỉ có 20% mua. (Xin lưu ý rằng giá trị "đã qua sử dụng" ở đây không có nghĩa là sách đã qua sử dụng.)

  • Viết một chương trình chấp nhận nhập một bộ sách (cho đến khi người dùng nhập người quản lý). Đối với mỗi cuốn sách yêu cầu: mã cho cuốn sách, chi phí sao chép một cuốn sách, số lượng sách hiện có, số lượng sách có thể đăng ký và dữ liệu cho biết cuốn sách đó là bắt buộc/không bắt buộc, mới/đã sử dụng trước đây hay không. Đầu ra, hãy cho thấy tất cả thông tin đầu vào trên một màn hình được định dạng phù hợp cùng với số lượng sách phải đặt hàng (nếu có, xin lưu ý rằng chỉ có sách mới được đặt hàng), tổng chi phí của mỗi đơn đặt hàng.

    Tiếp đến, sau khi nhập xong toàn bộ dữ liệu đầu vào, hãy cho thấy tổng chi phí của tất cả các đơn đặt hàng sách và lợi nhuận dự kiến nếu cửa hàng trả 80% giá niêm yết. Vì chúng ta chưa thảo luận về bất kỳ cách nào để xử lý một lượng lớn dữ liệu được đưa vào một chương trình (hãy chú ý theo dõi nhé!), bạn chỉ cần xử lý từng cuốn sách một rồi cho thấy màn hình kết quả của cuốn sách đó. Sau đó, khi người dùng nhập xong tất cả dữ liệu, chương trình của bạn sẽ cho ra kết quả tổng giá trị và giá trị lợi nhuận.

    Trước khi bạn bắt đầu viết mã, hãy dành chút thời gian để suy nghĩ về thiết kế của chương trình này. Phân rã thành một tập hợp các hàm và tạo một hàm main() có chức năng đọc như đề cương giải pháp cho vấn đề của bạn. Đảm bảo mỗi hàm thực hiện một tác vụ.

    Dưới đây là kết quả mẫu:

    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
    ***************************************************

Dự án cơ sở dữ liệu

Trong dự án này, chúng tôi tạo một chương trình C++ đầy đủ chức năng giúp triển khai một ứng dụng cơ sở dữ liệu đơn giản.

Chương trình này giúp chúng tôi quản lý cơ sở dữ liệu về các nhà soạn nhạc và thông tin liên quan về họ. Các tính năng của chương trình bao gồm:

  • Có thể thêm nhà soạn nhạc mới
  • Khả năng xếp hạng của một nhạc sĩ (tức là cho biết mức độ chúng tôi thích hoặc không thích bản nhạc của nhà soạn nhạc đó)
  • Có thể xem tất cả các nhà soạn nhạc trong cơ sở dữ liệu
  • Có thể xem tất cả các nhà soạn nhạc theo thứ hạng

"Có hai cách để xây dựng một thiết kế phần mềm: Một cách là làm cho quá trình đó trở nên đơn giản đến mức rõ ràng không có khiếm khuyết nào và cách còn lại là làm cho nó phức tạp đến mức không có khiếm khuyết rõ ràng. Phương pháp đầu tiên còn khó hơn nhiều". – C.A.R. Hoare

Nhiều người trong chúng ta đã học cách thiết kế và lập trình bằng phương pháp "theo quy trình". Câu hỏi chính mà chúng tôi bắt đầu là "Chương trình phải làm gì?". Chúng tôi phân rã giải pháp cho một vấn đề thành các nhiệm vụ, mỗi nhiệm vụ sẽ giải quyết một phần của vấn đề. Những nhiệm vụ này ánh xạ đến các hàm trong chương trình của chúng ta và được gọi tuần tự từ main() hoặc từ các hàm khác. Phương pháp từng bước này rất phù hợp cho một số vấn đề mà chúng tôi cần giải quyết. Nhưng thông thường, các chương trình của chúng tôi không chỉ là những nhiệm vụ hoặc sự kiện theo trình tự tuyến tính.

Với cách tiếp cận hướng đối tượng (OO), chúng ta bắt đầu bằng câu hỏi "Tôi đang lập mô hình đối tượng trong thế giới thực nào?" Thay vì chia chương trình thành các tác vụ như mô tả ở trên, chúng ta chia chương trình đó thành các mô hình gồm các đối tượng thực tế. Các đối tượng thực này có trạng thái được xác định bởi một tập hợp các thuộc tính và một tập hợp hành vi hoặc thao tác mà chúng có thể thực hiện. Các thao tác có thể làm thay đổi trạng thái của đối tượng hoặc có thể gọi các thao tác của đối tượng khác. Tiền đề cơ bản là một đối tượng "tự biết" cách tự làm mọi việc. 

Trong thiết kế OO, chúng tôi xác định các đối tượng thực tế dựa trên các lớp và đối tượng; thuộc tính và hành vi. Nhìn chung, có một số lượng lớn đối tượng trong một chương trình OO. Tuy nhiên, nhiều đối tượng trong số này về cơ bản là giống nhau. Hãy cân nhắc những điều sau:

Lớp là một tập hợp các thuộc tính và hành vi chung của một đối tượng, mà có thể tồn tại trong thế giới thực. Ở hình minh hoạ ở trên, chúng ta có một lớp Apple. Tất cả quả táo, bất kể loại nào, đều có các đặc điểm về màu sắc và hương vị. Chúng tôi cũng đã xác định một hành vi, trong đó Apple hiển thị các thuộc tính của mình.

Trong sơ đồ này, chúng ta đã xác định 2 đối tượng thuộc lớp Apple. Mỗi đối tượng có các thuộc tính và thao tác giống như lớp, nhưng đối tượng này sẽ xác định các thuộc tính cho một loại táo cụ thể. Ngoài ra, thao tác Hiển thị còn hiển thị các thuộc tính cho đối tượng cụ thể đó, ví dụ: "Xanh lục" và "Sour".

Một thiết kế OO bao gồm một tập hợp các lớp, dữ liệu liên kết với các lớp này và tập hợp thao tác mà các lớp có thể thực hiện. Chúng tôi cũng cần xác định cách các lớp tương tác. Hoạt động tương tác này có thể được thực hiện bởi các đối tượng của một lớp khi gọi hành động của đối tượng trong các lớp khác. Ví dụ: chúng ta có thể có một lớp AppleOutputer tạo ra màu sắc và giao diện của một mảng đối tượng Apple bằng cách gọi phương thức Display() của từng đối tượng Apple.

Dưới đây là các bước chúng ta thực hiện khi thiết kế OO:

  1. Xác định các lớp và xác định chung về nội dung mà một đối tượng của từng lớp lưu trữ dưới dạng dữ liệu và những việc mà một đối tượng có thể làm.
  2. Xác định các phần tử dữ liệu của mỗi lớp
  3. Xác định thao tác của từng lớp và cách triển khai một số thao tác của một lớp bằng thao tác của các lớp khác có liên quan.

Đối với hệ thống lớn, các bước này xảy ra lặp đi lặp lại ở các cấp độ chi tiết khác nhau.

Đối với hệ thống cơ sở dữ liệu trình soạn, chúng ta cần một lớp Composer đóng gói tất cả dữ liệu mà chúng ta muốn lưu trữ trên một trình soạn. Một đối tượng của lớp này có thể thăng cấp hoặc giảm hạng chính nó (thay đổi thứ hạng) và hiển thị các thuộc tính của lớp đó.

Chúng ta cũng cần một tập hợp các đối tượng Composer. Để làm được điều này, chúng ta xác định một lớp Cơ sở dữ liệu quản lý các bản ghi riêng lẻ. Một đối tượng của lớp này có thể thêm hoặc truy xuất các đối tượng Compose và hiển thị từng đối tượng riêng lẻ bằng cách gọi thao tác hiển thị của một đối tượng Composer.

Cuối cùng, chúng ta cần một số loại giao diện người dùng để cung cấp các thao tác tương tác trên cơ sở dữ liệu. Đây là một lớp giữ chỗ, tức là chúng tôi thực sự không biết giao diện người dùng sẽ trông như thế nào, nhưng chúng tôi biết mình sẽ cần một lớp như vậy. Đó có thể là hình ảnh đồ hoạ hoặc cũng có thể là văn bản. Hiện tại, chúng ta xác định một phần giữ chỗ mà chúng ta có thể điền vào sau này.

Giờ đây, chúng ta đã xác định được các lớp cho ứng dụng cơ sở dữ liệu trình kết hợp, bước tiếp theo là xác định các thuộc tính và thao tác cho các lớp đó. Trong một ứng dụng phức tạp hơn, chúng ta sẽ ngồi cùng bút chì và giấy hoặc UML hoặc thẻ CRC hoặc OOD để xác định hệ phân cấp lớp và cách các đối tượng tương tác.

Đối với cơ sở dữ liệu trình soạn, chúng ta xác định một lớp Composer chứa dữ liệu liên quan mà chúng ta muốn lưu trữ trên từng trình soạn. Lớp này cũng chứa các phương thức để thao túng thứ hạng và hiển thị dữ liệu.

Lớp Cơ sở dữ liệu cần một số loại cấu trúc để lưu giữ các đối tượng Composer. Chúng ta cần thêm được một đối tượng Composer mới vào cấu trúc, cũng như truy xuất một đối tượng cụ thể của Composer. Chúng tôi cũng muốn trình bày tất cả các đối tượng theo thứ tự mục nhập hoặc thứ hạng.

Lớp Giao diện người dùng triển khai giao diện dựa trên trình đơn, với các trình xử lý gọi hành động trong lớp Cơ sở dữ liệu. 

Nếu các lớp dễ hiểu và thuộc tính cũng như hành động rõ ràng, như trong ứng dụng trình kết hợp, thì việc thiết kế các lớp tương đối dễ dàng. Tuy nhiên, nếu bạn có thắc mắc về cách các lớp liên quan và tương tác, tốt nhất là bạn nên tìm hiểu kỹ càng trước khi bắt đầu lập trình.

Sau khi hiểu rõ về thiết kế và đánh giá thiết kế đó (sớm hơn về nội dung này), chúng tôi sẽ xác định giao diện cho từng lớp. Chúng tôi không lo lắng về thông tin triển khai tại thời điểm này – chỉ cần quan tâm đến các thuộc tính và thao tác, những phần nào của trạng thái và thao tác của lớp có sẵn cho các lớp khác.

Trong C++, chúng ta thường thực hiện việc này bằng cách xác định tệp tiêu đề cho từng lớp. Lớp Composer có các thành phần dữ liệu riêng tư cho mọi dữ liệu chúng ta muốn lưu trữ trên một trình kết hợp. Chúng ta cần có các phương thức truy cập ("phương thức lấy") và biến thể (các phương thức ("set") cũng như các thao tác chính cho lớp.

// 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_;
};

Lớp Cơ sở dữ liệu cũng rất đơn giản.

// 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_;
};

Hãy lưu ý cách chúng tôi đóng gói dữ liệu dành riêng cho nhà soạn nhạc một cách cẩn thận trong một lớp riêng. Chúng tôi có thể đặt một cấu trúc hoặc lớp trong lớp Cơ sở dữ liệu để đại diện cho bản ghi Composer và truy cập trực tiếp vào bản ghi đó. Tuy nhiên, đó sẽ là "dưới dạng đối tượng thấp", tức là chúng ta không lập mô hình bằng các đối tượng nhiều nhất có thể.

Bạn sẽ nhận thấy khi bắt đầu triển khai các lớp Composer và Database; việc có một lớp Composer riêng sẽ gọn gàng hơn nhiều. Cụ thể, việc có các toán tử nguyên tử riêng biệt trên đối tượng Composer sẽ đơn giản hoá đáng kể việc triển khai các phương thức Display() trong lớp Cơ sở dữ liệu.

Tất nhiên, cũng có một hoạt động như "đối tượng hoá quá mức", trong đó chúng ta cố gắng biến mọi thứ thành một lớp, hoặc chúng ta sẽ có nhiều lớp hơn mức cần thiết. Cần thực hành để tìm được sự cân bằng phù hợp và bạn sẽ thấy rằng mỗi lập trình viên sẽ có quan điểm khác nhau. 

Thông thường, bạn có thể xác định xem mình đang bị đối tượng quá mức hay quá mức bằng cách lập sơ đồ các lớp một cách cẩn thận. Như đã đề cập trước đó, bạn phải thiết kế lớp trước khi bắt đầu lập trình. Điều này có thể giúp bạn phân tích phương pháp của mình. Một ký hiệu phổ biến được dùng cho mục đích này là UML (Ngôn ngữ tạo mô hình hợp nhất) Hiện đã có các lớp được xác định cho đối tượng Composer và Cơ sở dữ liệu, chúng ta cần một giao diện cho phép người dùng tương tác với cơ sở dữ liệu. Một trình đơn đơn giản sẽ giúp bạn:

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

Chúng ta có thể triển khai giao diện người dùng dưới dạng một lớp hoặc dưới dạng một chương trình quy trình. Không phải mọi nội dung trong chương trình C++ đều phải là một lớp. Trên thực tế, nếu quá trình xử lý diễn ra theo tuần tự hoặc theo tác vụ, như trong chương trình trình đơn này, thì bạn có thể triển khai theo quy trình. Quan trọng là phải triển khai phương thức này sao cho vẫn là "phần giữ chỗ", tức là nếu muốn tạo giao diện người dùng đồ hoạ vào thời điểm nào đó, chúng ta không cần phải thay đổi bất cứ điều gì trong hệ thống, ngoại trừ giao diện người dùng.

Việc cuối cùng chúng ta cần hoàn tất đơn đăng ký là một chương trình để kiểm thử các lớp. Đối với lớp Composer, chúng ta cần một chương trình main() nhận dữ liệu đầu vào, điền sẵn đối tượng Compose rồi hiển thị đối tượng đó để đảm bảo lớp hoạt động đúng cách. Chúng ta cũng muốn gọi tất cả các phương thức của lớp 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();
}

Chúng ta cần một chương trình kiểm thử tương tự cho lớp 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();
}

Xin lưu ý rằng các chương trình kiểm thử đơn giản này là bước đầu tiên phù hợp, nhưng chúng yêu cầu chúng ta phải kiểm tra đầu ra theo cách thủ công để đảm bảo chương trình hoạt động chính xác. Khi một hệ thống lớn hơn, việc kiểm tra đầu ra theo cách thủ công sẽ nhanh chóng trở nên không hợp lý. Trong bài học tiếp theo, chúng ta sẽ giới thiệu chương trình kiểm thử tự kiểm tra dưới dạng kiểm thử đơn vị.

Thiết kế cho ứng dụng của chúng tôi hiện đã hoàn tất. Bước tiếp theo là triển khai các tệp .cpp cho các lớp và giao diện người dùng.Để bắt đầu, hãy tiếp tục và sao chép/dán mã .h cũng như mã trình điều khiển kiểm thử ở trên vào các tệp rồi biên dịch chúng.Sử dụng trình điều khiển kiểm thử để kiểm tra các lớp của bạn. Sau đó, hãy triển khai giao diện sau:

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

Sử dụng các phương thức mà bạn đã xác định trong lớp Cơ sở dữ liệu để triển khai giao diện người dùng. Đảm bảo phương thức của bạn không có lỗi. Ví dụ: thứ hạng phải luôn nằm trong phạm vi từ 1 đến 10. Cũng đừng cho phép người dùng thêm 101 nhà soạn nhạc, trừ phi bạn định thay đổi cấu trúc dữ liệu trong lớp Database.

Xin lưu ý rằng tất cả mã của bạn cần tuân thủ quy ước lập trình của chúng tôi. Các quy ước này sẽ được lặp lại ở đây để thuận tiện cho bạn:

  • Mỗi chương trình chúng tôi viết đều bắt đầu bằng một nhận xét tiêu đề, trong đó cung cấp tên tác giả, thông tin liên hệ, nội dung mô tả ngắn và cách sử dụng (nếu thích hợp). Mỗi hàm/phương thức bắt đầu bằng một nhận xét về hoạt động và cách sử dụng.
  • Chúng tôi thêm nhận xét giải thích bằng cách sử dụng câu đầy đủ bất cứ khi nào mã không ghi lại được (ví dụ: trong trường hợp quá trình xử lý có khó khăn, không rõ ràng, thú vị hoặc quan trọng).
  • Luôn sử dụng tên mô tả: biến là các từ viết thường được phân tách bằng _, như trong my_variable. Tên hàm/phương thức sử dụng chữ hoa để đánh dấu các từ, như trong MyExLưu hàm(). Các hằng số bắt đầu bằng "k" và sử dụng chữ cái viết hoa để đánh dấu các từ, như trong kDaysInWeek.
  • Thụt lề là bội số của 2. Cấp đầu tiên là 2 dấu cách; nếu cần thụt lề thêm, chúng tôi dùng 4 dấu cách, 6 dấu cách, v.v.

Chào mừng bạn đến với thế giới thực!

Trong mô-đun này, chúng tôi giới thiệu hai công cụ rất quan trọng dùng trong hầu hết các tổ chức kỹ thuật phần mềm. Thứ nhất là công cụ xây dựng và thứ hai là hệ thống quản lý cấu hình. Cả hai công cụ này đều cần thiết trong kỹ thuật phần mềm công nghiệp, trong đó nhiều kỹ sư thường làm việc trên một hệ thống lớn. Các công cụ này giúp điều phối và kiểm soát các thay đổi đối với cơ sở mã, đồng thời cung cấp phương tiện hiệu quả để biên dịch và liên kết hệ thống từ nhiều tệp tiêu đề và chương trình.

Tạo tệp

Quy trình xây dựng chương trình thường được quản lý bằng một công cụ bản dựng. Công cụ này sẽ biên dịch và liên kết các tệp cần thiết theo đúng thứ tự. Thông thường, các tệp C++ có các phần phụ thuộc, chẳng hạn như một hàm được gọi trong một chương trình nằm trong một chương trình khác. Hoặc có thể cần một tệp tiêu đề cho một vài tệp .cpp khác nhau. Một công cụ bản dựng sẽ tìm ra thứ tự biên dịch chính xác từ các phần phụ thuộc này. Công cụ này cũng sẽ chỉ biên dịch các tệp đã thay đổi kể từ phiên bản cuối cùng. Điều này có thể giúp tiết kiệm rất nhiều thời gian trong những hệ thống có hàng trăm hoặc hàng nghìn tệp.

Công cụ xây dựng nguồn mở có tên là make thường được sử dụng. Để tìm hiểu về tính năng này, hãy đọc bài viết này. Hãy xem liệu bạn có thể tạo biểu đồ phần phụ thuộc cho ứng dụng Composer Database hay không, sau đó dịch biểu đồ này thành tệp makefile.Đây là giải pháp của chúng tôi.

Hệ thống quản lý cấu hình

Công cụ thứ hai được dùng trong kỹ thuật phần mềm công nghiệp là Quản lý cấu hình (CM). Thông tin này được dùng để quản lý thay đổi. Giả sử Bob và Susan đều là người viết nội dung về công nghệ và cả hai đều đang cập nhật một cuốn hướng dẫn kỹ thuật. Trong cuộc họp, người quản lý sẽ chỉ định cho họ từng phần trong cùng một tài liệu để cập nhật.

Tài liệu hướng dẫn kỹ thuật được lưu trữ trên máy tính mà cả Bob và Susan đều có thể truy cập. Một số vấn đề có thể phát sinh nếu không có bất kỳ công cụ hoặc quy trình CM nào. Một trường hợp có thể xảy ra là máy tính lưu trữ tài liệu có thể được thiết lập sao cho Bob và Susan không thể cả hai thao tác trên tài liệu hướng dẫn cùng một lúc. Điều này sẽ làm chậm đáng kể.

Một tình huống nguy hiểm hơn sẽ phát sinh khi máy tính lưu trữ cho phép cả Bob và Susan mở tài liệu cùng lúc. Sau đây là những gì có thể xảy ra:

  1. Bob mở tài liệu trên máy tính và làm việc trên phần của mình.
  2. Susan mở tài liệu trên máy tính và làm việc trong phần của mình.
  3. Bob hoàn tất thay đổi của mình và lưu tài liệu vào máy tính lưu trữ.
  4. Susan hoàn tất thay đổi và lưu tài liệu vào máy tính lưu trữ.

Hình minh hoạ này cho thấy vấn đề có thể xảy ra nếu không có lựa chọn kiểm soát nào trên bản sao của tài liệu hướng dẫn kỹ thuật. Khi Susan lưu các thay đổi của mình, cô ấy sẽ ghi đè các thay đổi do Bob thực hiện.

Đây chính xác là loại tình huống mà hệ thống CM có thể kiểm soát. Với hệ thống Quản trị viên diễn đàn, cả Bob và Susan đều "xem" tài liệu hướng dẫn kỹ thuật của riêng mình rồi xử lý chúng. Khi Bob kiểm tra lại các thay đổi của mình, hệ thống sẽ biết rằng Susan đã thanh toán bản sao của riêng mình. Khi Susan kiểm tra bản sao của mình, hệ thống sẽ phân tích các thay đổi mà cả Bob và Susan đã thực hiện rồi tạo một phiên bản mới hợp nhất hai nhóm thay đổi này với nhau.

Các hệ thống CM có một số tính năng ngoài việc quản lý các thay đổi đồng thời như mô tả ở trên. Nhiều hệ thống lưu trữ bản lưu trữ của mọi phiên bản của một tài liệu, ngay từ lần đầu tiên tài liệu đó được tạo. Đối với sách hướng dẫn kỹ thuật, điều này có thể rất hữu ích khi người dùng có phiên bản hướng dẫn cũ và đang đặt câu hỏi cho một chuyên gia công nghệ. Hệ thống CM sẽ cho phép người viết công nghệ truy cập vào phiên bản cũ và có thể xem nội dung mà người dùng đang xem.

Hệ thống CM đặc biệt hữu ích trong việc kiểm soát các thay đổi được thực hiện đối với phần mềm. Những hệ thống như vậy được gọi là hệ thống Quản lý cấu hình phần mềm (SCM). Nếu bạn xem xét số lượng lớn các tệp mã nguồn riêng lẻ trong một tổ chức kỹ thuật phần mềm lớn và số lượng lớn kỹ sư phải thực hiện các thay đổi đối với các tệp đó, thì rõ ràng hệ thống SCM là tối quan trọng.

Quản lý cấu hình phần mềm

Các hệ thống SCM được dựa trên một ý tưởng đơn giản: bản sao chính thức của các tệp được lưu giữ trong kho lưu trữ trung tâm. Mọi người xem các bản sao của các tệp trong kho lưu trữ, xử lý những bản sao đó rồi kiểm tra lại khi hoàn tất. Hệ thống SCM quản lý và theo dõi các bản sửa đổi do nhiều người thực hiện so với một nhóm chính. 

Tất cả hệ thống SCM đều có các tính năng thiết yếu sau đây:

  • Quản lý đồng thời
  • Lập phiên bản
  • Đồng bộ hoá

Hãy cùng tìm hiểu kỹ hơn về từng tính năng này.

Quản lý đồng thời

Đồng thời là việc nhiều người chỉnh sửa đồng thời một tệp. Với một kho lưu trữ lớn, chúng tôi muốn mọi người có thể làm được điều này, nhưng điều này có thể gây ra một số vấn đề.

Hãy xem xét một ví dụ đơn giản trong miền kỹ thuật: Giả sử chúng tôi cho phép các kỹ sư sửa đổi đồng thời cùng một tệp trong kho lưu trữ trung tâm của mã nguồn. Cả Client1 và Client2 đều cần thực hiện các thay đổi đối với tệp cùng một lúc:

  1. Client1 mở bar.cpp.
  2. Client2 mở bar.cpp.
  3. Client1 thay đổi và lưu tệp.
  4. Client2 thay đổi tệp và lưu lại ghi đè các thay đổi của Client1.

Đương nhiên là chúng tôi không muốn điều này xảy ra. Ngay cả khi chúng tôi kiểm soát tình hình bằng cách yêu cầu hai kỹ sư xử lý các bản sao riêng biệt thay vì trực tiếp trên một nhóm chính (như trong hình minh hoạ bên dưới), các bản sao này phải được đối chiếu bằng cách nào đó. Hầu hết các hệ thống SCM xử lý vấn đề này bằng cách cho phép nhiều kỹ sư kiểm tra tệp ("đồng bộ hoá" hoặc "cập nhật") và thực hiện thay đổi nếu cần. Sau đó, hệ thống SCM sẽ chạy các thuật toán để hợp nhất các thay đổi khi các tệp được kiểm tra lại trong ("gửi" hoặc "cam kết") vào kho lưu trữ.

Các thuật toán này có thể đơn giản (yêu cầu các kỹ sư giải quyết các thay đổi gây mâu thuẫn) hoặc không quá đơn giản (xác định cách hợp nhất các thay đổi gây xung đột một cách thông minh và chỉ hỏi kỹ sư nếu hệ thống thực sự gặp sự cố). 

Lập phiên bản

Tạo phiên bản là quá trình theo dõi các bản sửa đổi tệp để có thể tạo lại (hoặc khôi phục) phiên bản trước của tệp. Bạn có thể thực hiện việc này bằng cách tạo bản sao lưu trữ của mọi tệp khi tệp đó được kiểm tra vào kho lưu trữ, hoặc bằng cách lưu mọi thay đổi đã thực hiện đối với tệp. Chúng tôi có thể sử dụng các bản lưu trữ hoặc thay đổi thông tin để tạo phiên bản trước đó bất cứ lúc nào. Các hệ thống tạo phiên bản cũng có thể tạo báo cáo nhật ký về những người đã kiểm tra nội dung thay đổi, thời điểm họ vào lại và nội dung thay đổi.

Đồng bộ hoá

Với một số hệ thống SCM, các tệp riêng lẻ được đánh dấu vào và ra khỏi kho lưu trữ. Những hệ thống mạnh mẽ hơn cho phép bạn xem nhiều tệp cùng một lúc. Các kỹ sư sẽ kiểm tra bản sao kho lưu trữ (hoặc một phần) hoàn chỉnh của riêng mình và xử lý các tệp nếu cần. Sau đó, họ cam kết các thay đổi của họ trở lại kho lưu trữ chính theo định kỳ và cập nhật các bản sao cá nhân của riêng họ để luôn cập nhật những thay đổi mà người khác đã thực hiện. Quá trình này gọi là đồng bộ hoá hoặc cập nhật.

Phiên bản phụ

Subversion (SVN) là một hệ thống quản lý phiên bản nguồn mở. Gói này có tất cả các tính năng như mô tả ở trên.

SVN sử dụng một phương pháp đơn giản khi xảy ra xung đột. Xung đột là khi hai hoặc nhiều kỹ sư thực hiện các thay đổi khác nhau cho cùng một khu vực của cơ sở mã và sau đó cả hai cùng gửi các thay đổi của mình. SVN chỉ thông báo cho các kỹ sư về việc có xung đột và các kỹ sư có trách nhiệm giải quyết xung đột đó.

Chúng tôi sẽ sử dụng SVN trong suốt khoá học này để giúp bạn làm quen với việc quản lý cấu hình. Những hệ thống như vậy rất phổ biến trong công nghiệp.

Bước đầu tiên là cài đặt SVN trên hệ thống của bạn. Hãy nhấp vào đây để xem hướng dẫn. Tìm hệ điều hành của bạn và tải tệp nhị phân thích hợp xuống.

Một số thuật ngữ SVN

  • Bản sửa đổi: Thay đổi trong tệp hoặc nhóm tệp. Bản sửa đổi là một "ảnh chụp nhanh" trong một dự án thay đổi liên tục.
  • Kho lưu trữ: Bản sao chính nơi SVN lưu trữ toàn bộ nhật ký sửa đổi của dự án. Mỗi dự án có một kho lưu trữ.
  • Bản sao làm việc: Bản sao mà một kỹ sư thực hiện các thay đổi đối với dự án. Có thể có nhiều bản sao công việc của một dự án cụ thể, mỗi bản do một kỹ sư cá nhân sở hữu.
  • Check Out: Để yêu cầu một bản sao hoạt động từ kho lưu trữ. Bản sao đang hoạt động bằng với trạng thái của dự án tại thời điểm thanh toán.
  • Cam kết: Gửi các thay đổi từ bản sao đang hoạt động của bạn vào kho lưu trữ trung tâm. Đây còn được gọi là xác nhận có mặt hoặc gửi.
  • Cập nhật: Để chuyển những thay đổi của người khác từ kho lưu trữ vào bản sao đang làm việc của bạn, hoặc để cho biết liệu bản sao đang làm việc của bạn có bất kỳ thay đổi nào chưa được xác nhận hay không. Việc này cũng giống như quá trình đồng bộ hoá như mô tả ở trên. Vì vậy, việc cập nhật/đồng bộ hoá sẽ cập nhật bản sao đang làm việc của bạn với bản sao kho lưu trữ.
  • Xung đột: Tình huống khi hai kỹ sư cố gắng thực hiện các thay đổi cho cùng một khu vực của tệp. SVN cho thấy xung đột, nhưng các kỹ sư phải giải quyết chúng.
  • Thông điệp nhật ký: Nhận xét mà bạn đính kèm vào một bản sửa đổi khi bạn thực hiện bản sửa đổi này. Nhận xét này mô tả các thay đổi của bạn. Nhật ký cung cấp một bản tóm tắt về những gì đang diễn ra trong một dự án.

Giờ đây khi bạn đã cài đặt SVN, chúng ta sẽ tìm hiểu một số lệnh cơ bản. Điều đầu tiên cần làm là thiết lập kho lưu trữ trong một thư mục cụ thể. Sau đây là các lệnh:

$ 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.

Lệnh import sao chép nội dung của thư mục mytree vào dự án thư mục trong kho lưu trữ. Chúng ta có thể xem thư mục trong kho lưu trữ bằng lệnh list

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

Việc nhập không tạo ra bản sao hoạt động. Để thực hiện việc này, bạn cần sử dụng lệnh svn checkout. Thao tác này sẽ tạo một bản sao hoạt động của cây thư mục. Hãy thực hiện việc đó ngay bây giờ:

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

Bây giờ, khi đã có một bản sao đang hoạt động, bạn có thể thực hiện các thay đổi đối với các tệp và thư mục trong đó. Bản sao làm việc của bạn cũng giống như mọi tập hợp tệp và thư mục khác – bạn có thể thêm tệp mới hoặc chỉnh sửa, di chuyển chúng, thậm chí xoá toàn bộ bản sao đang làm việc. Xin lưu ý rằng nếu sao chép và di chuyển tệp trong bản sao đang làm việc, bạn cần sử dụng chức năng svn copysvn move thay vì các lệnh của hệ điều hành. Để thêm một tệp mới, hãy sử dụng svn add và để xoá một tệp, hãy sử dụng svn delete (xoá svn). Nếu bạn chỉ muốn chỉnh sửa, chỉ cần mở tệp bằng trình chỉnh sửa rồi chỉnh sửa!

Có một số tên thư mục chuẩn thường dùng với Phiên bản phụ. Thư mục "đường dây" chứa dòng phát triển chính cho dự án của bạn. Thư mục "branches" (nhánh) chứa mọi phiên bản nhánh mà bạn có thể đang xử lý.

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

Giả sử bạn đã thực hiện mọi thay đổi cần thiết đối với bản sao làm việc và muốn đồng bộ hoá bản sao đó với kho lưu trữ. Nếu nhiều kỹ sư khác đang làm việc trong khu vực này của kho lưu trữ, thì điều quan trọng là bạn phải luôn cập nhật bản sao đang làm việc của mình. Bạn có thể sử dụng lệnh svn status (trạng thái svn) để xem các thay đổi mà bạn đã thực hiện.

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

Lưu ý rằng có rất nhiều cờ trên lệnh trạng thái để kiểm soát kết quả này. Nếu bạn muốn xem những thay đổi cụ thể trong tệp đã sửa đổi, hãy sử dụng svn diff.

$ 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;
...

Cuối cùng, để cập nhật bản sao làm việc từ kho lưu trữ, hãy sử dụng lệnh svn update.

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

Đây là một nơi có thể xảy ra xung đột. Trong kết quả trên, "U" cho biết không có thay đổi nào được thực hiện đối với các phiên bản lưu trữ của các tệp này và quá trình cập nhật đã được thực hiện. Chữ "G" có nghĩa là quá trình hợp nhất đã xảy ra. Phiên bản kho lưu trữ đã được thay đổi, nhưng các thay đổi này không xung đột với phiên bản của bạn. Chữ "C" cho biết có sự xung đột. Điều này có nghĩa là những thay đổi từ kho lưu trữ trùng lặp với những thay đổi của bạn và giờ bạn phải chọn giữa những thay đổi đó.

Đối với mỗi tệp có xung đột, Subversion sẽ đặt 3 tệp vào bản sao đang làm việc của bạn:

  • file.mine: Đây là tệp của bạn vì tệp này tồn tại trong bản sao đang làm việc của bạn trước khi bạn cập nhật bản sao đang làm việc.
  • file.rOLDREV: Đây là tệp mà bạn đã kiểm tra ra khỏi kho lưu trữ trước khi thực hiện các thay đổi.
  • file.rNEWREV: Tệp này là phiên bản hiện tại trong kho lưu trữ.

Bạn có thể làm một trong ba việc sau để giải quyết xung đột:

  • Kiểm tra các tệp và thực hiện hợp nhất theo cách thủ công.
  • Sao chép một trong các tệp tạm thời do SVN tạo ra qua phiên bản bản sao làm việc của bạn.
  • Chạy svn cancel để loại bỏ tất cả thay đổi của bạn.

Sau khi giải quyết xong xung đột, bạn thông báo cho SVN biết bằng cách chạy svn đã giải quyết. Thao tác này sẽ xoá 3 tệp tạm thời và SVN không còn xem tệp đó ở trạng thái xung đột.

Điều cuối cùng cần làm là cam kết phiên bản hoàn thiện của bạn vào kho lưu trữ. Bạn có thể thực hiện việc này bằng lệnh svn cam kết. Khi có một thay đổi, bạn cần cung cấp thông điệp nhật ký có nội dung mô tả các thay đổi. Thông điệp nhật ký này được đính kèm vào bản sửa đổi bạn tạo.

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

Có rất nhiều điều để tìm hiểu về SVN và cách SVN có thể hỗ trợ các dự án kỹ thuật phần mềm lớn. Có rất nhiều tài nguyên trên web – bạn chỉ cần tìm "Phiên bản phụ" trên Google.

Để thực hành, hãy tạo một kho lưu trữ cho hệ thống Cơ sở dữ liệu của trình soạn thảo rồi nhập tất cả tệp của bạn. Sau đó, kiểm tra một bản sao đang hoạt động và thực hiện các lệnh được mô tả ở trên.

Tài liệu tham khảo

Cuốn sách trực tuyến phiên bản phụ

Bài viết trên Wikipedia về SVN

Trang web phiên bản phụ

Ứng dụng: Một nghiên cứu về giải phẫu

Hãy xem chương trình eSkeletons của Đại học Texas tại Austin