Skip to content Skip to navigation

Connexions

You are here: Home » Content » Lớp và đối tượng

Navigation

Lenses

What is a lens?

Definition of a lens

Lenses

A lens is a custom view of the content in the repository. You can think of it as a fancy kind of list that will let you see content through the eyes of organizations and people you trust.

What is in a lens?

Lens makers point to materials (modules and collections), creating a guide that includes their own comments and descriptive tags about the content.

Who can create a lens?

Any individual member, a community, or a respected organization.

What are tags? tag icon

Tags are descriptors added by lens makers to help label content, attaching a vocabulary that is meaningful in the context of the lens.

This content is ...

Affiliated with (What does "Affiliated with" mean?)

This content is either by members of the organizations listed or about topics related to the organizations listed. Click each link to see a list of all content affiliated with the organization.
  • VOCW

    This module is included inLens: Vietnam OpenCourseWare's Lens
    By: Vietnam OpenCourseWare

    Click the "VOCW" link to see all content affiliated with them.

Recently Viewed

This feature requires Javascript to be enabled.
 

Lớp và đối tượng

Module by: Lê Thị Mỹ Hạnh. E-mail the author

Summary: Phần này trình bày về lớp và đối tượng

DẪN NHẬP

Bây giờ chúng ta bắt đầu tìm hiểu về lập trình hướng đối tượng trong C++. Trong các phần sau, chúng ta cũng tìm hiểu về các kỹ thuật của thiết kế hướng đối tượng (Object-Oriented Design OOD): Chúng ta phân tích một vấn đề cụ thể, xác định các đối tượng nào cần để cài đặt hệ thống, xác định các thuộc tính nào mà đối tượng phải có, xác định hành vi nào mà đối tượng cần đưa ra, và chỉ rõ làm thế nào các đối tượng cần tương tác với đối tượng khác để thực hiện các mục tiêu tổng thể của hệ thống.

Chúng ta nhắc lại các khái niệm và thuật ngữ chính của định hướng đối tượng. OOP đóng gói dữ liệu (các thuộc tính) và các hàm (hành vi) thành gói gọi là các đối tượng. Dữ liệu và các hàm của đối tượng có sự liên hệ mật thiết với nhau. Các đối tượng có các đặc tính của việc che dấu thông tin. Điều này nghĩa là mặc dù các đối tượng có thể biết làm thế nào liên lạc với đối tượng khác thông qua các giao diện hoàn toàn xác định, bình thường các đối tượng không được phép biết làm thế nào các đối tượng khác được thực thi, các chi tiết của sự thi hành được dấu bên trong các đối tượng.

Trong C và các ngôn ngữ lập trình thủ tục, lập trình có khuynh hướng định hướng hành động, trong khi ý tưởng trong lập trình C++ là định hướng đối tượng. Trong C, đơn vị của lập trình là hàm; trong C++, đơn vị của lập trình là lớp (class) .

Các lập trình viên C tập trung vào viết các hàm. Các nhóm của các hành động mà thực hiện vài công việc được tạo thành các hàm, và các hàm được nhóm thành các chương trình. Dữ liệu thì rất quan trọng trong C, nhưng quan điểm là dữ liệu tồn tại chính trong việc hỗ trợ các hành động mà hàm thực hiện. Các động từ trong một hệ thống giúp cho lập trình viên C xác định tập các hàm mà sẽ hoạt động cùng với việc thực thi hệ thống.

Các lập trình viên C++ tập trung vào việc tạo ra "các kiểu do người dùng định nghĩa" (user-defined types) gọi là các lớp. Các lớp cũng được tham chiếu như "các kiểu do lập trình viên định nghĩa" (programmer-defined types). Mỗi lớp chứa dữ liệu cũng như tập các hàm mà xử lý dữ liệu. Các thành phần dữ liệu của một lớp được gọi là "các thành viên dữ liệu" (data members). Các thành phần hàm của một lớp được gọi là "các hàm thành viên" (member functions). Giống như thực thể của kiểu có sẵn như int được gọi là một biến, một thực thể của kiểu do người dùng định nghĩa (nghĩa là một lớp) được gọi là một đối tượng. Các danh từ trong một hệ thống giúp cho lập trình viên C++ xác định tập các lớp. Các lớp này được sử dụng để tạo các đối tượng mà sẽ sẽ hoạt động cùng với việc thực thi hệ thống.

Các lớp trong C++ được tiến hóa tự nhiên của khái niệm struct trong C. Trước khi tiến hành việc trình bày các lớp trong C++, chúng ta tìm hiểu về cấu trúc, và chúng ta xây dựng một kiểu do người dùng định nghĩa dựa trên một cấu trúc.

CÀI ĐẶT MỘT KIỂU DO NGƯỜI DÙNG ĐỊNH NGHĨA VỚI MỘT struct

Ví dụ 3.1: Chúng ta xây dựng kiểu cấu trúc Time với ba thành viên số nguyên: Hour, Minute và second. Chương trình định nghĩa một cấu trúc Time gọi là DinnerTime. Chương trình in thời gian dưới dạng giờ quân đội và dạng chuẩn.

#include <iostream.h>

struct Time

{

int Hour; // 0-23

int Minute; // 0-59

int Second; // 0-59

};

void PrintMilitary(const Time &); //prototype

void PrintStandard(const Time &); //prototype

int main()

{

Time DinnerTime;

//Thiet lap cac thanh vien voi gia tri hop le

DinnerTime.Hour = 18;

DinnerTime.Minute = 30;

DinnerTime.Second = 0;

cout << "Dinner will be held at ";

PrintMilitary(DinnerTime);

cout << " military time," << endl << "which is ";

PrintStandard(DinnerTime);

cout << " standard time." << endl;

//Thiet lap cac thanh vien voi gia tri khong hop le

DinnerTime.Hour = 29;

DinnerTime.Minute = 73;

DinnerTime.Second = 103;

cout << endl << "Time with invalid values: ";

PrintMilitary(DinnerTime);

cout << endl;

return 0;

}

//In thoi gian duoi dang gio quan doi

void PrintMilitary(const Time &T)

{

cout << (T.Hour < 10 ? "0" : "") << T.Hour << ":"

<< (T.Minute < 10 ? "0" : "") << T.Minute << ":"

<< (T.Second < 10 ? "0" : "") << T.Second;

}

//In thoi gian duoi dang chuan

void PrintStandard(const Time &T)

{

cout << ((T.Hour == 12) ? 12 : T.Hour % 12)

<< ":" << (T.Minute < 10 ? "0" : "") << T.Minute

<< ":" << (T.Second < 10 ? "0" : "") << T.Second

<< (T.Hour < 12 ? " AM" : " PM");

}

Chúng ta chạy ví dụ 3.1, kết quả ở hình 3.1

Hình 1
Hình 1 (graphics1.png)

Hình 3.1: Kết quả của ví dụ 3.1

Có một vài hạn chế khi tạo các kiểu dữ liệu mới với các cấu trúc ở phần trên. Khi việc khởi tạo không được yêu cầu, có thể có dữ liệu chưa khởi tạo và các vấn đề nảy sinh. Ngay cả nếu dữ liệu được khởi tạo, nó có thể khởi tạo không chính xác. Các giá trị không hợp lệ có thể được gán cho các thành viên của một cấu trúc bởi vì chương trình trực tiếp truy cập dữ liệu. Chẳng hạn ở ví dụ 3.1 ở dòng 23 đến dòng 25, chương trình gán các giá trị không hợp lệ cho đối tượng DinnerTime. Nếu việc cài đặt của struct thay đổi, tất cả các chương trình sử dụng struct phải thay đổi. Điều này do lập trình viên trực tiếp thao tác kiểu dữ liệu. Không có "giao diện" để bảo đảm lập trình viên sử dụng dữ liệu chính xác và bảo đảm dữ liệu còn lại ở trạng thái thích hợp. Mặt khác, cấu trúc trong C không thể được in như một đơn vị, chúng được in khi các thành viên được in. Các cấu trúc trong C không thể so sánh với nhau, chúng phải được so sánh thành viên với thành viên.

Phần sau cài đặt lại cấu trúc Time ở ví dụ 3.1 như một lớp và chứng minh một số thuận lợi để việc tạo ra cái gọi là các kiểu dữ liệu trừu tượng (Abstract Data Types – ADT) như các lớp. Chúng ta sẽ thấy rằng các lớp và các cấu trúc có thể sử dụng gần như giống nhau trong C++. Sự khác nhau giữa chúng là thuộc tính truy cập các thành viên.

CÀI ĐẶT MỘT  KIỂU DỮ LIỆU TRỪU TƯỢNG VỚI MỘT LỚP

Các lớp cho phép lập trình viên mô hình các đối tượng mà có các thuộc tính (biểu diễn như các thành viên dữ liệu – Data members) và các hành vi hoặc các thao tác (biểu diễn như các hàm thành viên – Member functions). Các kiểu chứa các thành viên dữ liệu và các hàm thành viên được định nghĩa thông thường trong C++ sử dụng từ khóa class, có cú pháp như sau:

class <class-name>

{

<member-list> //Thân của lớp

};

Trong đó:

class-name: tên lớp.

member-list: đặc tả các thành viên dữ liệu và các hàm thành viên.

Các hàm thành viên đôi khi được gọi là các phương thức (methods) trong các ngôn ngữ lập trình hướng đối tượng khác, và được đưa ra trong việc đáp ứng các message gởi tới một đối tượng. Một message tương ứng với việc gọi hàm thành viên.

Khi một lớp được định nghĩa, tên lớp có thể được sử dụng để khai báo đối tượng của lớp theo cú pháp sau:

<class-name> <object-name>;

Chẳng hạn, cấu trúc Time sẽ được định nghĩa dưới dạng lớp như sau:

class Time

{

public:

Time();

void SetTime(int, int, int)

void PrintMilitary();

void PrintStandard()

private:

int Hour; // 0 - 23

int Minute; // 0 - 59

int Second; // 0 - 59

};

Trong định nghĩa lớp Time chứa ba thành viên dữ liệu là Hour, Minute và Second, và cũng trong lớp này, chúng ta thấy các nhãn public và private được gọi là các thuộc tính xác định truy cập thành viên (member access specifiers) gọi tắt là thuộc tính truy cập.

Bất kỳ thành viên dữ liệu hay hàm thành viên khai báo sau public có thể được truy cập bất kỳ nơi nào mà chương trình truy cập đến một đối tượng của lớp. Bất kỳ thành viên dữ liệu hay hàm thành viên khai báo sau private chỉ có thể được truy cập bởi các hàm thành viên của lớp. Các thuộc tính truy cập luôn luôn kết thúc với dấu hai chấm (:) và có thể xuất hiện nhiều lần và theo thứ tự bất kỳ trong định nghĩa lớp. Mặc định thuộc tính truy cập là private.

Định nghĩa lớp chứa các prototype của bốn hàm thành viên sau thuộc tính truy cập public là Time(), SetTime(), PrintMilitary() và PrintStandard(). Đó là các hàm thành viên public (public member function) hoặc giao diện (interface) của lớp. Các hàm này sẽ được sử dụng bởi các client (nghĩa là các phần của một chương trình mà là các người dùng) của lớp xử lý dữ liệu của lớp. Có thể nhận thấy trong định nghĩa lớp Time, hàm thành viên Time() có cùng tên với tên lớp Time, nó được gọi là hàm xây dựng (constructor function) của lớp Time.

Một constructor là một hàm thành viên đặc biệt mà khởi động các thành viên dữ liệu của một đối tượng của lớp. Một constructor của lớp được gọi tự động khi đối tượng của lớp đó được tạo.

Thông thường, các thành viên dữ liệu được liệt kê trong phần private của một lớp, còn các hàm thành viên được liệt kê trong phần public. Nhưng có thể có các hàm thành viên private và thành viên dữ liệu public.

Khi lớp được định nghĩa, nó có thể sử dụng như một kiểu trong phần khai báo như sau:

Time Sunset, // Đối tượng của lớp Time

ArrayTimes[5], // Mảng các đối tượng của lớp Time

*PTime, // Con trỏ trỏ đến một đối tượng của lớp Time

&DinnerTime = Sunset; // Tham chiếu đến một đối tượng của lớp Time

Ví dụ 3.2: Xây dựng lại lớp Time ở ví dụ 3.1

1: #include <iostream.h>

2:

3: class Time

4: {

5:  public:

6: Time();  //Constructor

7: void SetTime(int, int, int); //Thiết lập Hour, Minute va Second

8: void PrintMilitary(); //In thời gian dưới dạng giờ quân đội

9: void PrintStandard(); //In thời gian dưới dạng chuẩn

10: private:

11: int Hour; // 0 - 23

12: int Minute; // 0 - 59

13: int Second; // 0 - 59

14: };

15:

16: //Constructor khởi tạo mỗi thành viên DL với giá trị zero

17: //Bảo đảm tất cả các đối tượng bắt đầu ở một t.thái thích hợp

18: Time::Time()

19: {

20: Hour = Minute = Second = 0;

21: }

22:

23: //Thiết lập một giá trị Time mới sử dụng giờ quânđội

24: //Thực hiện việc kiểm tra tính hợp lệ trên các giá trị dữ liệu

25: //Thiết lập các giá trị không hợp lệ thành zero

26: void Time::SetTime(int H, int M, int S)

27: {

28:    Hour = (H >= 0 && H < 24) ? H : 0;

29:    Minute = (M >= 0 && M < 60) ? M : 0;

30:    Second = (S >= 0 && S < 60) ? S : 0;

31: }

32:

33: //In thời gian dưới dạng giờ quân đội

34: void Time::PrintMilitary()

35: {

36:    cout << (Hour < 10 ? "0" : "") << Hour << ":"

37:         << (Minute < 10 ? "0" : "") << Minute << ":"

38:         << (Second < 10 ? "0" : "") << Second;

39: }

40:

41: //In thời gian dưới dạng chuẩn

42: void Time::PrintStandard()

43: {

44:    cout << ((Hour == 0 || Hour == 12) ? 12 : Hour % 12)

44:         << ":" << (Minute < 10 ? "0" : "") << Minute

45:         << ":" << (Second < 10 ? "0" : "") << Second

46:         << (Hour < 12 ? " AM" : " PM");

48: }

49:

50: int main()

51: {

52:    Time T; //Đối tượng T của lớp Time

53:

54:    cout << "The initial military time is ";

55:    T.PrintMilitary();

56:    cout << endl << "The initial standard time is ";

57:    T.PrintStandard();

58:

59:    T.SetTime(13, 27, 6);

60:    cout << endl << endl << "Military time after SetTime is ";

61:    T.PrintMilitary();

62:    cout << endl << "Standard time after SetTime is ";

63:    T.PrintStandard();

64:

65:    T.SetTime(99, 99, 99); //Thử thiết lập giá trị không hợp lệ

66:    cout << endl << endl << "After attempting invalid settings:"

67:          << endl << "Military time: ";

68:    T.PrintMilitary();

69:    cout << endl << "Standard time: ";

70:    T.PrintStandard();

71:    cout << endl;

72:    return 0;

73: }

Chúng ta chạy ví dụ 3.2, kết quả ở hình 3.2

Hình 2
Hình 2 (graphics2.png)

Hình 3.2: Kết quả của ví dụ 3.2

Trong ví dụ 3.2, chương trình thuyết minh một đối tượng của lớp Time gọi là T (dòng 52). Khi đó constructor của lớp Time tự động gọi và rõ ràng khởi tạo mỗi thành viên dữ liệu private là zero. Sau đó thời gian được in dưới dạng giờ quân đội và dạng chuẩn để xác nhận các thành viên này được khởi tạo thích hợp (dòng 54 đến 57). Kế tới thời gian được thiết lập bằng cách sử dụng hàm thành viên SetTime() (dòng 59) và thời gian lại được in ở hai dạng (dòng 60 đến 63). Cuối cùng hàm thành viên SetTime() (dòng 65) thử thiết lập các thành viên dữ liệu với các giá trị không hợp lệ, và thời gian lại được in ở hai dạng (dòng 66 đến 70).

Chúng ta nhận thấy rằng, tất cả các thành viên dữ liệu của một lớp không thể khởi tạo tại nơi mà chúng được khai báo trong thân lớp. Các thành viên dữ liệu này phải được khởi tạo bởi constructor của lớp hay chúng có thể gán giá trị bởi các hàm thiết lập.

Khi một lớp được định nghĩa và các hàm thành viên của nó được khai báo, các hàm thành viên này phải được định nghĩa. Mỗi hàm thành viên của lớp có thể được định nghĩa trực tiếp trong thân lớp (hiển nhiên bao gồm prototype hàm của lớp), hoặc hàm thành viên có thể được định nghĩa sau thân lớp. Khi một hàm thành viên được định nghĩa sau định nghĩa lớp tương ứng, tên hàm được đặt trước bởi tên lớp và toán tử định phạm vi (::). Chẳng hạn như ở ví dụ 3.2 gồm các dòng 18, 26, 34 và 42. Bởi vì các lớp khác nhau có thể có các tên thành viên giống nhau, toán tử định phạm vi "ràng buộc" tên thành viên tới tên lớp để nhận dạng các hàm thành viên của một lớp.

Mặc dù một hàm thành viên khai báo trong định nghĩa một lớp có thể định nghĩa bên ngoài định nghĩa lớp này, hàm thành viên đó vẫn còn bên trong phạm vi của lớp, nghĩa là tên của nó chỉ được biết tới các thành viên khác của lớp ngoại trừ tham chiếu thông qua một đối tượng của lớp, một tham chiếu tới một đối tượng của lớp, hoặc một con trỏ trỏ tới một đối tượng của lớp.

Nếu một hàm thành viên được định nghĩa trong định nghĩa một lớp, hàm thành viên này chính là hàm inline. Các hàm thành viên định nghĩa bên ngoài định nghĩa một lớp có thể là hàm inline bằng cách sử dụng từ khóa inline.

Hàm thành viên cùng tên với tên lớp nhưng đặt trước là một ký tự ngã (~) được gọi là destructor của lớp này. Hàm destructor làm "công việc nội trợ kết thúc" trên mỗi đối tượng của lớp trước khi vùng nhờ cho đối tượng được phục hồi bởi hệ thống.

Ví dụ 3.3: Lấy lại ví dụ 3.2 nhưng hai hàm PrintMilitary() và PrintStandard() là các hàm inline.

1: #include <iostream.h>

2:

3: class Time

4: {

5: public:

6: Time(); ; //Constructor

7: void SetTime(int, int, int); //Thiết lập Hour, Minute va Second

8: void PrintMilitary() // In thời gian dưới dạng giờ quânđội

9: {

10: cout << (Hour < 10 ? "0" : "") << Hour << ":"

11:          << (Minute < 10 ? "0" : "") << Minute << ":"

12:          << (Second < 10 ? "0" : "") << Second;

13: }

14: void PrintStandard(); // In thời gian dưới dạng chuẩn

15: private:

16: int Hour; // 0 - 23

17: int Minute; // 0 - 59

18: int Second; // 0 - 59

19: };

20: //Constructor khởi tạo mỗi thành viên dữ liệu với giá trị zero

21: //Bảo đảm t.cả các đối tượng bắt đầu ở một trạng thái thích hợp

22: Time::Time()

23: {

24: Hour = Minute = Second = 0;

25: }

26:

27: #9; //Thiết lập một giá trị Time mới sử dụng giờ quân đội

28: #9; //T.hiện việc k.tra tính hợp lệ trên các giá trị DL

29: #9; //Thiết lập các giá trị không hợp lệ thành zero

30: void Time::SetTime(int H, int M, int S)

31: {

32: Hour = (H >= 0 && H < 24) ? H : 0;

33: Minute = (M >= 0 && M < 60) ? M : 0;

34: Second = (S >= 0 && S < 60) ? S : 0;

35: }

36:

37: #9; //In thời gian dưới dạng chuẩn

38: inline void Time::PrintStandard()

39: {

40: cout << ((Hour == 0 || Hour == 12) ? 12 : Hour % 12)

41:       << ":" << (Minute < 10 ? "0" : "") << Minute

42:       << ":" << (Second < 10 ? "0" : "") << Second

43: << (Hour < 12 ? " AM" : " PM");

44: }

45:

46: int main()

47: {

48: Time T;

49:

50: cout << "The initial military time is ";

51: T.PrintMilitary();

52: cout << endl << "The initial standard time is ";

53: T.PrintStandard();

54:

55: T.SetTime(13, 27, 6);

56: cout << endl << endl << "Military time after SetTime is ";

57: T.PrintMilitary();

58: cout << endl << "Standard time after SetTime is ";

59: T.PrintStandard();

60:

61: T.SetTime(99, 99, 99); //Thử thiết lập giá trị không hợp lệ

62: cout << endl << endl << "After attempting invalid settings:"

63:       << endl << "Military time: ";

64: T.PrintMilitary();

65: cout << endl << "Standard time: ";

66: T.PrintStandard();

67: cout << endl;

68: return 0;

69: }

Chúng ta chạy ví dụ 3.3, kết quả ở hình 3.3

Hình 3
Hình 3 (graphics3.png)

Hình 3.3: Kết quả của ví dụ 3.3

PHẠM VI LỚP VÀ TRUY CẬP CÁC THÀNH VIÊN LỚP

Các thành viên dữ liệu của một lớp (các biến khai báo trong định nghĩa lớp) và các hàm thành viên (các hàm khai báo trong định nghĩa lớp) thuộc vào phạm vi của lớp.

Trong một phạm vi lớp, các thành viên của lớp được truy cập ngay lập tức bởi tất cả các hàm thành viên của lớp đó và có thể được tham chiếu một cách dễ dàng bởi tên. Bên ngoài một phạm vi lớp, các thành viên của lớp được tham chiếu thông qua hoặc một tên đối tượng, một tham chiếu đến một đối tượng, hoặc một con trỏ tới đối tượng.

Các hàm thành viên của lớp có thể được đa năng hóa (overload), nhưng chỉ bởi các hàm thành viên khác của lớp. Để đa năng hóa một hàm thành viên, đơn giản cung cấp trong định nghĩa lớp một prototype cho mỗi phiên bản của hàm đa năng hóa, và cung cấp một định nghĩa hàm riêng biệt cho mỗi phiên bản của hàm.

Các hàm thành viên có phạm vi hàm trong một lớp – các biến định nghĩa trong một hàm thành viên chỉ được biết tới hàm đó. Nếu một hàm thành viên định nghĩa một biến cùng tên với tên một biến trong phạm vi lớp, biến phạm vi lớp được dấu bởi biến phạm vi hàm bên trong phạm vi hàm. Như thế một biến bị dấu có thể được truy cập thông qua toán tử định phạm vi.

Các toán tử được sử dụng để truy cập các thành viên của lớp được đồng nhất với các toán tử sử dụng để truy cập các thành viên của cấu trúc. Toán tử lựa chọn thành viên dấu chấm (.) được kết hợp với một tên của đối tượng hay với một tham chiếu tới một đối tượng để truy cập các thành viên của đối tượng. Toán tử lựa chọn thành viên mũi tên (->)được kết hợp với một con trỏ trỏ tới một truy cập để truy cập các thành viên của đối tượng.

Ví dụ 3.4: Chương trình sau minh họa việc truy cập các thành viên của một lớp với các toán tử lựa chọn thành viên.

1: #include <iostream.h>

2:

3: class Count

4: {

5:     public:

6:     int X;

7:     void Print()

8:     {

9:         cout << X << endl;

10:     }

11: };

12:

13: int main()

14: {

15:     Count Counter, //Tạo đối tượng Counter

16:     *CounterPtr = &Counter, //Con trỏ trỏ tới Counter

17:     &CounterRef = Counter; //Tham chiếu tới Counter

18:

19:     cout << "Assign7 to X and Print using the object's name: ";

20:     Counter.X = 7; //Gán 7 cho thành viên dữ liệu X

21:     Counter.Print(); //Gọi hàm thành viên Print

22:

23:     cout << "Assign 8 to X and Print using a reference: ";

24:     CounterRef.X = 8; //Gán 8 cho thành viên dữ liệu X

25:     CounterRef.Print(); //Gọi hàm thành viên Print

26:

27:     cout << "Assign 10 to X and Print using a pointer: ";

28:     CounterPtr->X = 10; // Gán 10 cho thành viên dữ liệu X

29:     CounterPtr->Print(); //Gọi hàm thành viên Print

30:     return 0;

31: }

Chúng ta chạy ví dụ 3.4, kết quả ở hình 3.4

Hình 4
Hình 4 (graphics4.png)

Hình 3.4: Kết quả của ví dụ 3.4

ĐIỀU KHIỂN TRUY CẬP TỚI CÁC THÀNH VIÊN

Các thuộc tính truy cập public và private (và protected chúng ta sẽ xem xét sau) được sử dụng để điều khiển truy cập tới các thành viên dữ liệu và các hàm thành viên của lớp. Chế độ truy cập mặc định đối với lớp là private vì thế tất cả các thành viên sau phần header của lớp và trước nhãn đầu tiên là private. Sau mỗi nhãn, chế độ mà được kéo theo bởi nhãn đó áp dụng cho đến khi gặp nhãn kế tiếp hoặc cho đến khi gặp dấu móc phải (}) của phần định nghĩa lớp. Các nhãn public, private và protected có thể được lặp lại nhưng cách dùng như vậy thì hiếm có và có thể gây khó hiểu.

Các thành viên private chỉ có thể được truy cập bởi các hàm thành viên (và các hàm friend) của lớp đó. Các thành viên public của lớp có thể được truy cập bởi bất kỳ hàm nào trong chương trình.

Mục đích chính của các thành viên public là để biểu thị cho client của lớp một cái nhìn của các dịch vụ (services) mà lớp cung cấp. Tập hợp này của các dịch vụ hình thành giao diện public của lớp. Các client của lớp không cần quan tâm làm thế nào lớp hoàn thành các thao tác của nó. Các thành viên private của lớp cũng như các định nghĩa của các hàm thành viên public của nó thì không phải có thể truy cập tới client của một lớp. Các thành phần này hình thành sự thi hành của lớp.

Ví dụ 3.5: Chương trình sau cho thấy rằng các thành viên private chỉ có thể truy cập thông qua giao diện public sử dụng các hàm thành viên public.

#include <iostream.h>

class MyClass

{

private:

int X,Y;

public:

void Print();

};

void MyClass::Print()

{

cout <<X<<Y<<endl;

}

int main()

{

MyClass M;

M.X = 3;

M.Y = 4;

M.Print();

return 0;

}

Khi chúng ta biên dịch chương trình này, compiler phát sinh ra hai lỗi tại hai dòng 20 và 21 như sau:

Hình 5
Hình 5 (graphics5.png)

Hình 3.5: Thông báo lỗi của ví dụ 3.5

Thuộc tính truy cập mặc định đối với các thành viên của lớp là private. Thuộc tính truy cập các thành viên của một lớp có thể được thiết lập rõ ràng là public, protected hoặc private. Thuộc tính truy cập mặc định đối với các thành viên của struct là public. Thuộc tính truy cập các thành viên của một struct cũng có thể được thiết lập rõ ràng là public, protected hoặc private.

Truy cập đến một dữ liệu private cần phải được điều khiển cẩn thận bởi việc sử dụng của các hàm thành viên, gọi là các hàm truy cập (access functions).

CÁC HÀM TRUY CẬP VÀ CÁC HÀM TIỆN ÍCH

Không phải tất cả các hàm thành viên đều là public để phục vụ như bộ phận giao diện của một lớp. Một vài hàm còn lại là private và phục vụ như các hàm tiện ích (utility functions) cho các hàm khác của lớp.

Các hàm truy cập có thể đọc hay hiển thị dữ liệu. Sử dụng các hàm truy cập để kiểm tra tính đúng hoặc sai của các điều kiện – các hàm như thế thường được gọi là các hàm khẳng định (predicate functions). Một ví dụ của hàm khẳng định là một hàm IsEmpty() của lớp container - một lớp có khả năng giữ nhiều đối tượng - giống như một danh sách liên kết, một stack hay một hàng đợi. Một chương trình sẽ kiểm tra hàm IsEmpty() trước khi thử đọc mục khác từ đối tượng container.

Một hàm tiện ích không là một phần của một giao diện của lớp. Hơn nữa nó là một hàm thành viên private mà hỗ trợ các thao tác của các hàm thành viên public. Các hàm tiện ích không dự định được sử dụng bởi các client của lớp.

Ví dụ 3.6:  Minh họa cho các hàm tiện ích.

1: #include <iostream.h>

2: #include <iomanip.h>

3:

4: class SalesPerson

5: {

6: public:

7: SalesPerson();             //constructor

8: void SetSales(int, double);//Ng.dùng cung cấp các hình của

9: #9; #9; //những hàng bán của một tháng

10: void PrintAnnualSales();

11:

12: private:

13: double Sales[12]; //12 hình của những hàng bán hằng tháng

14: double TotalAnnualSales(); //Hàm tiện ích

15: };

16:

17: //Hàm constructor khởi tạo mảng

18: SalesPerson::SalesPerson()

19: {

20: for (int I = 0; I < 12; I++)

21: Sales[I] = 0.0;

22: }

23:

24://Hàm th.lập một trong 12 hình của những hàng bán hằng tháng

25: void SalesPerson::SetSales(int Month, double Amount)

26: {

27: if (Month >= 1 && Month <= 12 && Amount > 0)

28: Sales[Month - 1] = Amount;

29: else

30: cout << "Invalid month or sales figure" << endl;

31: }

32:

33: //Hàm tiện íchđể tính tổng hàng bán hằng năm

34: double SalesPerson::TotalAnnualSales()

35: {

36: double Total = 0.0;

37:

38: for (int I = 0; I < 12; I++)

39: Total += Sales[I];

40: return Total;

41: }

42:

43: //In tổng hàng bán hằng năm

44: void SalesPerson::PrintAnnualSales()

45: {

46: cout << setprecision(2)

47:       << setiosflags(ios::fixed | ios::showpoint)

48:       << endl << "The total annual sales are: $"

49:       << TotalAnnualSales() << endl;

50: }

51:

52: int main()

53: {

54: SalesPerson S;

55: double salesFigure;

56:

57: for (int I = 1; I <= 12; I++)

58: {

59: cout << "Enter sales amount for month "<< I << ": ";

60: cin >> salesFigure;

61: S.SetSales(I, salesFigure);

62: }

63: S.PrintAnnualSales();

64: return 0;

65: }

Chúng ta chạy ví dụ 3.6 , kết quả ở hình 3.6

Hình 6
Hình 6 (graphics6.png)

Hình 3.6: Kết quả của ví dụ 3.6

KHỞI ĐỘNG CÁC ĐỐI TƯỢNG CỦA LỚP : CONSTRUCTOR

Khi một đối tượng được tạo, các thành viên của nó có thể được khởi tạo bởi một hàm constructor. Một constructor là một hàm thành viên với tên giống như tên của lớp. Lập trình viên cung cấp constructor mà được gọi tự động mỗi khi đối tượng của lớp đó được tạo. Các thành viên dữ liệu của một lớp không thể được khởi tạo trong định nghĩa của lớp. Hơn nữa, các thành viên dữ liệu phải được khởi động hoặc trong một constructor của lớp hoặc các giá trị của chúng có thể được thiết lập sau sau khi đối tượng được tạo. Các constructor không thể mô tả các kiểu trả về hoặc các giá trị trả về. Các constructor có thể được đa năng hóa để cung cấp sự đa dạng để khởi tạo các đối tượng của lớp.

Constructor có thể chứa các tham số mặc định. Bằng cách cung cấp các tham số mặc định cho constructor, ngay cả nếu không có các giá trị nào được cung cấp trong một constructor thì đối tượng vẫn được bảo đảm để trong một trạng thái phù hợp vì các tham số mặc định. Một constructor của lập trình viên cung cấp mà hoặc tất cả các tham số của nó có giá trị mặc định hoặc không có tham số nào được gọi là constructor mặc định (default constructor). Chỉ có thể có một constructor mặc định cho mỗi lớp.

Ví dụ 3.7: Constructor với các tham số mặc định

#include <iostream.H>

class Time

{

public:

Time(int = 0, int = 0, int = 0); //Constructor mac dinh

void SetTime(int, int, int);

void PrintMilitary();

void PrintStandard();

private:

int Hour;

int Minute;

int Second;

};

//Ham constructor de khoi dong du lieu private

//Cac gia tri mac dinh la 0

Time::Time(int Hr, int Min, int Sec)

{

SetTime(Hr, Min, Sec);

}

//Thiet lap cac gia tri cua Hour, Minute va Second

//Gia tri khong hop le duoc thiet lap la 0

void Time::SetTime(int H, int M, int S)

{

Hour = (H >= 0 && H < 24) ? H : 0;

Minute = (M >= 0 && M < 60) ? M : 0;

Second = (S >= 0 && S < 60) ? S : 0;

}

//Hien thi thoi gian theo dang gio quan doi: HH:MM:SS

void Time::PrintMilitary()

{

cout << (Hour < 10 ? "0" : "") << Hour << ":"

<< (Minute < 10 ? "0" : "") << Minute << ":"

<< (Second < 10 ? "0" : "") << Second;

}

//Hien thi thoi gian theo dang chuan: HH:MM:SS AM (hoac PM)

void Time::PrintStandard()

{

cout << ((Hour == 0 || Hour == 12) ? 12 : Hour % 12)

<< ":" << (Minute < 10 ? "0" : "") << Minute

<< ":" << (Second < 10 ? "0" : "") << Second

<< (Hour < 12 ? " AM" : " PM");

}

int main()

{

Time T1,T2(2),T3(21,34),T4(12,25,42),T5(27,74,99);

cout << "Constructed with:" << endl << "all arguments defaulted:" << endl << " ";

T1.PrintMilitary();cout << endl << " ";

T1.PrintStandard();

cout << endl << "Hour specified; Minute and Second defaulted:" << endl << " ";

T2.PrintMilitary();cout << endl << " ";

T2.PrintStandard();

cout << endl << "Hour and Minute specified; Second defaulted:" << endl << " ";

T3.PrintMilitary();cout << endl << " ";

T3.PrintStandard();cout << endl << "Hour, Minute, and Second specified:"<<endl<<" ";

T4.PrintMilitary(); cout << endl << " ";

T4.PrintStandard();cout << endl << "all invalid values specified:" << endl << " ";

T5.PrintMilitary();cout << endl << " ";

T5.PrintStandard();cout << endl;

return 0;

}

Chương trình ở ví dụ 3.7 khởi tạo năm đối tượng của lớp Time (ở dòng 52). Đối tượng T1 với ba tham số lấy giá trị mặc định, đối tượng T2 với một tham số được mô tả, đối tượng T3 với hai tham số được mô tả, đối tượng T4 với ba tham số được mô tả và đối tượng T5 với các tham số có giá trị không hợp lệ.

Chúng ta chạy ví dụ 3.7, kết quả ở hình 3.7

Hình 7
Hình 7 (graphics7.png)

Hình 3.7: Kết quả của ví dụ 3.7

Nếu không có constructor nào được định nghĩa trong một lớp thì trình biên dịch tạo một constructor mặc định. Constructor này không thực hiện bất kỳ sự khởi tạo nào, vì vậy khi đối tượng được tạo, nó không bảo đảm để trong một trạng thái phù hợp.

SỬ DỤNG DESTRUCTOR

Một destructor là một hàm thành viên đặc biệt của một lớp. Tên của destructor đối với một lớp là ký tự ngã (~) theo sau bởi tên lớp.

Destructor của một lớp được gọi khi đối tượng được hủy bỏ nghĩa là khi sự thực hiện chương trình rời khỏi phạm vi mà trong đó đối tượng của lớp đó được khởi tạo. Destructor không thực sự hủy bỏ đối tượng – nó thực hiện "công việc nội trợ kết thúc" trước khi hệ thống phục hồi không gian bộ nhớ của đối tượng để nó có thể được sử dụng giữ các đối tượng mới.

Một destructor không nhận các tham số và không trả về giá trị. Một lớp chỉ có duy nhất một destructor – đa năng hóa destructor là không cho phép.

Nếu trong một lớp không có định nghĩa một destructor thì trình biên dịch sẽ tạo một destructor mặc định không làm gì cả.

Ví dụ 3.8: Lớp có hàm destructor

#include <iostream.h>

class Simple

{private:

int *X;

public:

Simple(); //Constructor

~Simple(); //Destructor

void SetValue(int V);

int GetValue();

};

Simple::Simple()

{ X = new int; //Cấp phát vùng nhớ cho X

}

Simple::~Simple()

{

delete X; //Giải phóng vùng nhớ khi đối tượng bị hủy bỏ.

}

void Simple::SetValue(int V)

{

*X = V;

}

int Simple::GetValue()

{

return *X;

}

int main()

{

Simple S;

int X;

cout<<"Enter a number:";

cin>>X;

S.SetValue(X);

cout<<"The value of this number:"<<S.GetValue();

return 0;

}

Chúng ta chạy ví dụ 3.8, kết quả ở hình 3.8

Hình 8
Hình 8 (graphics8.png)

Hình 3.8: Kết quả của ví dụ 3.8

KHI NÀO CÁC CONSTRUTOR VÀ DESTRUCTOR  ĐƯỢC GỌI ?

Các constructor và destructor được gọi một cách tự động. Thứ tự các hàm này được gọi phụ thuộc vào thứ tự trong đó sự thực hiện vào và rời khỏi phạm vi mà các đối tượng được khởi tạo. Một cách tổng quát, các destructor được gọi theo thứ tự ngược với thứ tự của các constructor được gọi.

Các constructor được gọi của các đối tượng khai báo trong phạm vi toàn cục trước bất kỳ hàm nào (bao gồm hàm main()) trong file mà bắt đầu thực hiện. Các destructor tương ứng được gọi khi hàm main() kết thúc hoặc hàm exit() được gọi.

Các constructor của các đối tượng cục bộ tự động được gọi khi sự thực hiện đến điểm mà các đối tượng được khai báo. Các destructor tương ứng được gọi khi các đối tượng rời khỏi phạm vi (nghĩa là khối mà trong đó chúng được khai báo). Các constructor và destructor đối với các đối tượng cục bộ tự động được gọi mỗi khi các đối tượng vào và rời khỏi phạm vi.

Các constructor được gọi của các đối tượng cục bộ tĩnh (static) khi sự thực hiện đến điểm mà các đối tượng được khai báo lần đầu tiên. Các destructor tương ứng được gọi khi hàm main() kết thúc hoặc hàm exit() được gọi.

Ví dụ 3.9: Chương trình sau minh họa thứ tự các constructor và destructor được gọi.

#include <iostream.h>

class CreateAndDestroy

{

public:

CreateAndDestroy(int); //Constructor

~CreateAndDestroy(); //Destructor

private:

int Data;

};

CreateAndDestroy::CreateAndDestroy(int Value)

{

Data = Value;

cout << "Object " << Data << " constructor";

}

CreateAndDestroy::~CreateAndDestroy()

{

cout << "Object " << Data << " destructor " << endl;

}

void Create(void); //Prototype

CreateAndDestroy First(1); //Doi tuong toan cuc

int main()

{

cout << " (global created before main)" << endl;

CreateAndDestroy Second(2); //Doi tuong cuc bo

cout << " (local automatic in main)" << endl;

static CreateAndDestroy Third(3); //Doi tuong cuc bo

cout << " (local static in main)" << endl;

Create(); //Goi ham de tao cac doi tuong

CreateAndDestroy Fourth(4); //Doi tuong cuc bo

cout << " (local automatic in main)" << endl;

return 0;

}

//Ham tao cac doi tuong

void Create(void)

{

CreateAndDestroy Fifth(5);

cout << " (local automatic in create)" << endl;

static CreateAndDestroy Sixth(6);

cout << " (local static in create)" << endl;

CreateAndDestroy Seventh(7);

cout << " (local automatic in create)" << endl;

}

Chương trình khai báo First ở phạm vi toàn cục. Constructor của nó được gọi khi chương trình bắt đầu thực hiện và destructor của nó được gọi lúc chương trình kết thúc sau tất cả các đối tượng khác được hủy bỏ. Hàm main() khai báo ba đối tượng. Các đối tượng Second và Fourth là các đối tượng cục bộ tự động và đối tượng Third là một đối tượng cục bộ tĩnh. Các constructor của các đối tượng này được gọi khi chương trình thực hiện đến điểm mà mỗi đối tượng được khai báo. Các destructor của các đối tượng Fourth và Second được gọi theo thứ tự này khi kết thúc của main() đạt đến. Vì đối tượng Third là tĩnh, nó tồn tại cho đến khi chương trình kết thúc. Destructor của đối tượng Third được gọi trước destructor của First nhưng sau tất cả các đối tượng khác được hủy bỏ.

Hàm Create() khai báo ba đối tượng – Fifth và Seventh là các đối tượng cục bộ tự động và Sixth là một đối tượng cục bộ tĩnh. Các destructor của các đối tượng Seventh và Fifth được gọi theo thứ tự này khi kết thúc của create() đạt đến. Vì đối tượng Sixth là tĩnh, nó tồn tại cho đến khi chương trình kết thúc. Destructor của đối tượng Sixth được gọi trước các destructor của Third và First nhưng sau tất cả các đối tượng khác được hủy bỏ.

Chúng ta chạy ví dụ 3.9, kết quả ở hình 3.9

Hình 9
Hình 9 (graphics9.png)

Hình 3.9: Kết quả của ví dụ 3.9

SỬ DỤNG CÁC THÀNH VIÊN DỮ LIỆU VÀ CÁC HÀM THÀNH VIÊN

Các thành viên dữ liệu private chỉ có thể được xử lý bởi các hàm thành viên (hay hàm friend) của lớp. Các lớp thường cung cấp các hàm thành viên public để cho phép các client của lớp để thiết lập (set) (nghĩa là "ghi") hoặc lấy (get) (nghĩa là "đọc") các giá trị của các thành viên dữ liệu private. Các hàm này thường không cần phải được gọi "set" hay "get", nhưng chúng thường đặt tên như vậy. Chẳng hạn, một lớp có thành viên dữ liệu private có tên InterestRate, hàm thành viên thiết lập giá trị có tên là SetInterestRate() và hàm thành viên lấy giá trị có tên là GetInterestRate(). Các hàm "Get" cũng thường được gọi là các hàm chất vấn (query functions).

Nếu một thành viên dữ liệu là public thì thành viên dữ liệu có thể được đọc hoặc ghi tại bất kỳ hàm nào trong chương trình. Nếu một thành viên dữ liệu là private, một hàm "get" public nhất định cho phép các hàm khác để đọc dữ liệu nhưng hàm get có thể điều khiển sự định dạng và hiển thị của dữ liệu. Một hàm "set" public có thể sẽ xem xét cẩn thận bất kỳ cố gắng nào để thay đổi giá trị của thành viên dữ liệu. Điều này sẽ bảo đảm rằng giá trị mới thì tương thích đối với mục dữ liệu. Chẳng hạn, một sự cố gắng thiết lập ngày của tháng là 37 sẽ bị loại trừ.

Các lợi ích của sự toàn vẹn dữ liệu thì không tự động đơn giản bởi vì các thành viên dữ liệu được tạo là private – lập trình viên phải cung cấp sự kiểm tra hợp lệ. Tuy nhiên C++ cung cấp một khung làm việc trong đó các lập trình viên có thể thiết kế các chương trình tốt hơn.

Client của lớp phải được thông báo khi một sự cố gắng được tạo ra để gán một giá trị không hợp lệ cho một thành viên dữ liệu. Chính vì lý do này, các hàm "set" của lớp thường được viết trả về các giá trị cho biết rằng một sự cố gắng đã tạo ra để gán một dữ liệu không hợp lệ cho một đối tượng của lớp. Điều này cho phép các client của lớp kiểm tra các giá trị trả về để xác định nếu đối tượng mà chúng thao tác là một đối tượng hợp lệ và để bắt giữ hoạt động thích hợp nếu đối tượng mà chúng thao tác thì không phải hợp lệ.

Ví dụ 3.10: Chương trình mở rộng lớp Time ở ví dụ 3.2 bao gồm hàm get và set đối với các thành viên dữ liệu private là hour, minute và second.

1: #include <iostream.h>

2:

3: class Time

4: {

5: public:

6: Time(int = 0, int = 0, int = 0); //Constructor

7: //Các hàm set

8: void SetTime(int, int, int); //Thiết lập Hour, Minute, Second

9: void SetHour(int); //Thiết lập Hour

10: void SetMinute(int); //Thiết lập Minute

11: void SetSecond(int); //Thiết lập Second

12: //Các hàm get

13: int GetHour();    //Trả về Hour

14: int GetMinute();    //Trả về Minute

15: int GetSecond();    //Trả về Second

16:

17: void PrintMilitary(); //Xuất thời gian theo dạng giờ quânđội

18: void PrintStandard(); //Xuất thời gian theo dạng chuẩn

19:

20: private:

21: int Hour; //0 - 23

22: int Minute; //0 - 59

23: int Second; //0 – 59

24: };

25:

26: //Constructor khởiđộng dữ liệu private

27: //Gọi hàm thành viên SetTime() để thiết lập các biến

24: //Các giá trị mặc định là 0

25: Time::Time(int Hr, int Min, int Sec)

26: {

27: SetTime(Hr, Min, Sec);

28: }

29:

30: //Thiết lập các giá trị của Hour, Minute, và Second

31: void Time::SetTime(int H, int M, int S)

32: {

33: Hour = (H >= 0 && H < 24) ? H : 0;

34: Minute = (M >= 0 && M < 60) ? M : 0;

35: Second = (S >= 0 && S < 60) ? S : 0;

36: }

37:

38: //Thiết lập giá trị của Hour

39: void Time::SetHour(int H)

40: {

41: Hour = (H >= 0 && H < 24) ? H : 0;

42: }

43:

44: //Thiết lập giá trị của Minute

45: void Time::SetMinute(int M)

46: {

47: Minute = (M >= 0 && M < 60) ? M : 0;

48: }

49:

50: //Thiết lập giá trị của Second

51: void Time::SetSecond(int S)

52: {

53: Second = (S >= 0 && S < 60) ? S : 0;

54: }

55:

56: //Lấy giá trị của Hour

57: int Time::GetHour()

58: {

59: return Hour;

60: }

61:

62: //Lấy giá trị của Minute

63: int Time::GetMinute()

64: {

65: return Minute;

66: }

67:

68: //Lấy giá trị của Second

69: int Time::GetSecond()

70: {

71: return Second;

72: }

73:

74: //Hiển thị thời gian dạng giờ quânđội: HH:MM:SS

75: void Time::PrintMilitary()

76: {

77: cout << (Hour < 10 ? "0" : "") << Hour << ":"

78:       << (Minute < 10 ? "0" : "") << Minute << ":"

79:       << (Second < 10 ? "0" : "") << Second;

80: }

81:

83: //Hiển thị thời gian dạng chuẩn: HH:MM:SS AM (hay PM)

84: void Time::PrintStandard()

85: {

86: cout << ((Hour == 0 || Hour == 12) ? 12 : Hour % 12) << ":"

87:       << (Minute < 10 ? "0" : "") << Minute << ":"

88:       << (Second < 10 ? "0" : "") << Second

89:       << (Hour < 12 ? " AM" : " PM");

90: }

91:

92: void IncrementMinutes(Time &, const int); //prototype

93:

94: int main()

95: {

96: Time T;

97:

99: T.SetHour(17);

100: T.SetMinute(34);

101: T.SetSecond(25);

102 cout << "Result of setting all valid values:" << endl

103:       << " Hour: " << T.GetHour()

104:       << " Minute: " << T.GetMinute()

105:       << " Second: " << T.GetSecond() << endl << endl;

106: T.SetHour(234); //Hour không hợp lệđược thiết lập bằng 0

107: T.SetMinute(43);

108: T.SetSecond(6373); //Second không hợp lệđược thiết lập bằng 0

109: cout << "Result of attempting to set invalid Hour and"

110:       << " Second:" << endl << " Hour: " << T.GetHour()

111:       << " Minute: " << T.GetMinute()

112:       << " Second: " << T.GetSecond() << endl << endl;

113: T.SetTime(11, 58, 0);

114: IncrementMinutes(T, 3);

115: return 0;

116: }

117:

118: void IncrementMinutes(Time &TT, const int Count)

119: {

120: cout << "Incrementing Minute " << Count

121:       << " times:" << endl << "Start time: ";

122: TT.PrintStandard();

123: for (int I = 1; I <= Count; I++)

124: {

125: TT.SetMinute((TT.GetMinute() + 1) % 60);

126: if (TT.GetMinute() == 0)

127: TT.SetHour((TT.GetHour() + 1) % 24);

128: cout << endl << "Minute + 1: ";

129: TT.PrintStandard();

130: }

131: cout << endl;

132: }

Trong ví dụ trên chúng ta có hàm IncrementMinutes() là hàm dùng để tăng Minite. Đây là hàm không thành viên mà sử dụng các hàm thành viên get và set để tăng thành viên Minite.

Chúng ta chạy ví dụ .10, kết quả ở hình 3.10

Hình 10
Hình 10 (graphics10.png)

Hình 3.10: Kết quả của ví dụ 3.10

TRẢ VỀ MỘT THAM CHIẾU TỚI MỘT THÀNH VIÊN DỮ LIỆU PRIVATE

Một tham chiếu tới một đối tượng là một bí danh của chính đối tượng đó và do đó có thể được sử dụng ở vế trái của phép gán. Trong khung cảnh đó, tham chiếu tạo một lvalue được chấp nhận hoàn toàn mà có thể nhận một giá trị. Một cách để sử dụng khả năng này (thật không may!) là có một hàm thành viên public của lớp trả về một tham chiếu không const tới một thành viên dữ liệu private của lớp đó.

Ví dụ 3.11: Chương trình sau sử dụng một phiên bản đơn giản của lớp Time để minh họa trả về một tham chiếu tới một dữ liệu private.

1: #include <iostream.h>

2:

3: class Time

4: {

5: public:

6: Time(int = 0, int = 0, int = 0);

7: void SetTime(int, int, int);

8: int GetHour();

9: int &BadSetHour(int); //Nguy hiểm trả về tham chiếu !!!

10: private:

11: int Hour;

12: int Minute;

13: int Second;

14: };

15:

16: //Constructor khởiđộng dữ liệu private

17: //Gọi hàm thành viên SetTime()để thiết lập các biến

18: //Các giá trị mặcđịnh là 0

19: Time::Time(int Hr, int Min, int Sec)

20: {

21: SetTime(Hr, Min, Sec);

22: }

23: //Thiết lập các giá trị của Hour, Minute, và Second

24: void Time::SetTime(int H, int M, int S)

25: {

26: Hour = (H >= 0 && H < 24) ? H : 0;

27: Minute = (M >= 0 && M < 60) ? M : 0;

28: Second = (S >= 0 && S < 60) ? S : 0;

29: }

30:

31: //Lấy giá trị của Hour

32: int Time::GetHour()

33: {

34: return Hour;

35: }

36:

37: //KHÔNG NÊN LẬP TRÌNH THEO KIỂU NÀY !!!

38: //Trả về một tham chiếu tới một thành viên dữ liệu private

39: int &Time::BadSetHour(int HH)

40: {

41: Hour = (HH >= 0 && HH < 24) ? HH : 0;

42: return Hour; //Nguy hiểm trả về tham chiếu !!!

43: }

44:

45: int main()

46: {

47: Time T;

48: int &HourRef = T.BadSetHour(20);

49:

50: cout << "Hour before modification: " << HourRef << endl;

51: HourRef = 30; //Thayđổi với giá trị không hợp lệ

52: cout << "Hour after modification: " << T.GetHour() << endl;

53: // Nguy hiểm: Hàm trả về một tham chiếu

54: //có thểđược sử dụng như một lvalue

55: T.BadSetHour(12) = 74;

56: cout << endl << "*********************************" << endl

57:       << "BAD PROGRAMMING PRACTICE!!!!!!!!!" << endl

58:       << "BadSetHour as an lvalue, Hour: "

59:       << T.GetHour()

60:       << endl << "*********************************" << endl;

61: return 0;

62: }

Trong chương trình hàm BadSetHour() trả về một tham chiếu tới thành viên dữ liệu Hour.

Chúng ta chạy ví dụ 3.11, kết quả ở hình 3.11

Hình 11
Hình 11 (graphics11.png)

Hình 3.11: Kết quả của ví dụ 3.11

PHÉP GÁN BỞI TOÁN TỬ SAO CHÉP THÀNH VIÊN MẶC ĐỊNH

Toán tử gán (=) được sử dụng để gán một đối tượng cho một đối tượng khác của cùng một kiểu. Toán tử gán như thế bình thường được thực hiện bởi toán tử sao chép thành viên (Memberwise copy) – Mỗi thành viên của một đối tượng được sao chép riêng rẽ tới cùng thành viên ở đối tượng khác (Chú ý rằng sao chép thành viên có thể phát sinh các vấn đề nghiêm trọng khi sử dụng với một lớp mà thành viên dữ liệu chứa vùng nhớ cấp phát động).

Các đối tượng có thể được truyền cho các tham số của hàm và có thể được trả về từ các hàm. Như thế việc truyền và trả về được thực hiện theo truyền giá trị – một sao chép của đối tượng được truyền hay trả về.:

Ví dụ 3.12: Chương trình sau minh họa toán tử sao chép thành viên mặc định

2: #include <iostream.h>

3: //Lớp Date đơn giản

4: class Date

5: {

6: public:

7: Date(int = 1, int = 1, int = 1990); //Constructor mặc định

8: void Print();

9: private:

10: int Month;

11: int Day;

12: int Year;

13: };

14:

15: //Constructor Date đơn giản với việc không kiểm tra miền

16: Date::Date(int m, int d, int y)

17: {

18: Month = m;

19: Day = d;

20: Year = y;

21: }

22:

23: //In Date theo dạng mm-dd-yyyy

24: void Date::Print()

25: {

26: cout << Month << '-' << Day << '-' << Year;

27: }

28:

29: int main()

30: {

31: Date Date1(7, 4, 1993), Date2; //Date2 mặc định là 1/1/90

32: cout << "Date1 = ";

33: Date1.Print();

34: cout << endl << "Date2 = ";

35: Date2.Print();

36: Date2 = Date1; //Gán bởi toán tử sao chép thành viên mặc định

37: cout << endl << endl

38:       << "After default memberwise copy, Date2 = ";

39: Date2.Print();

40: cout << endl;

41: return 0;

42: }

Chúng ta chạy ví dụ 3.12, kết quả ở hình 3.12

Hình 12
Hình 12 (graphics12.png)

Hình 3.12: Kết quả của ví dụ 3.12

CÁC ĐỐI TƯỢNG HẰNG VÀ CÁC HÀM THÀNH VIÊN CONST

Một vài đối tượng cần được thay đổi và một vài đối tượng thì không. Lập trình viên có thể sử dụng từ khóa const để cho biết đối tượng không thể thay đổi được, và nếu có cố gắng thay đổi đối tượng thì xảy ra lỗi. Chẳng hạn:

const Time Noon(12,0,0); //Khai báo một đối tượng const

Các trình biên dịch C++ lưu ý đến các khai báo const vì thế các trình biên dịch cấm hoàn toàn bất kỳ hàm thành viên nào gọi các đối tượng const (Một vài trình biên dịch chỉ cung cấp một cảnh báo). Điều này thì khắc nghiệt bởi vì các client của đối tượng hầu như chắc chắn sẽ muốn sử dụng các hàm thành viên "get" khác nhau với đối tượng, và tất nhiên không thể thay đổi đối tượng. Để cung cấp cho điều này, lập trình viên có thể khai báo các hàm thành viên const; điều này chỉ có thể thao tác trên các đối tượng const. Dĩ nhiên các hàm thành viên const không thể thay đổi đối tượng - trình biên dịch cấm điều này.

Một hàm được mô tả như const khi cả hai trong phần khai báo và trong phần định nghĩa của nó được chèn thêm từ khóa const sau danh sách các tham số của hàm, và trong trường hợp của định nghĩa hàm trước dấu ngoặc móc trái ({) mà bắt đầu thân hàm. Chẳng hạn, hàm thành viên của lớp A nào đó:

int A::GetValue() const

{

return PrivateDataMember;

}

Nếu một hàm thành viên const được định nghĩa bên ngoài định nghĩa của lớp thì khai báo hàm và định nghĩa hàm phải bao gồm const ở mỗi phần.

Một vấn đề nảy sinh ở đây đối với các constructor và destructor, mỗi hàm thường cần thay đổi đối tượng. Khai báo const không yêu cầu đối với các constructor và destructor của các đối tượng const. Một constructor phải được phép thay đổi một đối tượng mà đối tượng có thể được khởi tạo thích hợp. Một destructor phải có khả năng thực hiện vai trò "công việc kết thúc nội trợ" trước khi đối tượng được hủy.

Ví dụ 3.13: Chương trình sau sử dụng một lớp Time với các đối tượng const và các hàm thành viên const.

2: #include <iostream.h>

3: class Time

4: {

5: public:

6: Time(int = 0, int = 0, int = 0); //Constructor mặc định

7: //Các hàm set

8: void SetTime(int, int, int); //Thiết lập thời gian

9: void SetHour(int); //Thiết lập Hour

10: void SetMinute(int); //Thiết lập Minute

11: void SetSecond(int); //Thiết lập Second

12: //Các hàm get

13: int GetHour() const; //Trả về Hour

14: int GetMinute() const; //Trả về Minute

15: int GetSecond() const; //Trả về Second

16: //Các hàm in

17: void PrintMilitary() const; //In t.gian theo dạng giờ quân đội

18: void PrintStandard() const; //In thời gian theo dạng giờ chuẩn

19: private:

20: int Hour; //0 - 23

21: int Minute; //0 - 59

22: int Second; //0 – 59

23: };

24:

25: //Constructor khởi động dữ liệu private

26: //Các giá trị mặc định là 0

27: Time::Time(int hr, int min, int sec)

28: {

29: SetTime(hr, min, sec);

30: }

31:

32: //Thiết lập các giá trị của Hour, Minute, và Second

33: void Time::SetTime(int h, int m, int s)

34: {

35: Hour = (h >= 0 && h < 24) ? h : 0;

36: Minute = (m >= 0 && m < 60) ? m : 0;

37: Second = (s >= 0 && s < 60) ? s : 0;

38: }

39:

40: //Thiết lập giá trị của Hour

41: void Time::SetHour(int h)

42: {

43: Hour = (h >= 0 && h < 24) ? h : 0;

44: }

45:

46: //Thiết lập giá trị của Minute

47: void Time::SetMinute(int m)

48: {

49: Minute = (m >= 0 && m < 60) ? m : 0;

50: }

51:

52: //Thiết lập giá trị của Second

53: void Time::SetSecond(int s)

54: {

55: Second = (s >= 0 && s < 60) ? s : 0;

56: }

57:

58: //Lấy giá trị của Hour

59: int Time::GetHour() const

60: {

61: return Hour;

62: }

63:

64: //Lấy giá trị của Minute

65: int Time::GetMinute() const

66: {

67: return Minute;

68: }

69:

70: //Lấy giá trị của Second

71: int Time::GetSecond() const

72: {

73: return Second;

74: }

75:

76: //Hiển thị thời gian dạng giờ quân đội: HH:MM:SS

77: void Time::PrintMilitary() const

78: {

79: cout << (Hour < 10 ? "0" : "") << Hour << ":"

80:      << (Minute < 10 ? "0" : "") << Minute << ":"

81:      << (Second < 10 ? "0" : "") << Second;

82: }

83:

84: //Hiển thị thời gian dạng chuẩn: HH:MM:SS AM (hay PM)

85: void Time::PrintStandard() const

86: {

87: cout << ((Hour == 12) ? 12 : Hour % 12) << ":"

88:       << (Minute < 10 ? "0" : "") << Minute << ":"

89:       << (Second < 10 ? "0" : "") << Second

90:       << (Hour < 12 ? " AM" : " PM");

91: }

92:

93: int main()

94: {

95: const Time T(19, 33, 52); //Đối tượng hằng

96: T.SetHour(12); //ERROR: non-const member function

97: T.SetMinute(20); //ERROR: non-const member function

98: T.SetSecond(39); //ERROR: non-const member function

99: return 0;

100: }

Chương trình này khai báo một đối tượng hằng của lớp Time và cố gắng sửa đổi đối tượng với các hàm thành viên không hằng SetHour(), SetMinute() và SetSecond(). Các lỗi cảnh báo được phát sinh bởi trình biên dịch (Borland C++) như hình 3.13.

Hình 13
Hình 13 (graphics13.png)

Hình 3.13: Các cảnh báo của chương trình ở ví dụ 3.13

Lưu ý: Hàm thành viên const có thể được đa năng hóa với một phiên bản non-const. Việc lựa chọn hàm thành viên đa năng hóa nào để sử dụng được tạo một cách tự động bởi trình biên dịch dựa vào nơi mà đối tượng được khai báo const hay không.

Một đối tượng const không thể được thay đổi bởi phép gán vì thế nó phải được khởi động. Khi một thành viên dữ liệu của một lớp được khai báo const, một bộ khởi tạo thành viên (member initializer) phải được sử dụng để cung cấp cho constructor với giá trị ban đầu của thành viên dữ liệu đối với một đối tượng của lớp.

Ví dụ 3.14: C.trình sau sử dụng một bộ khởi tạo thành viên để khởi tạo một hằng của kiểu dữ liệu có sẵn.

2: #include <iostream.h>

3: class IncrementClass

4: {

5: public:

6: IncrementClass (int C = 0, int I = 1);

7: void AddIncrement()

8: {

9: Count += Increment;

10: }

11: void Print() const;

12: private:

13: int Count;

14: const int Increment; //Thành viên dữ liệu const

15: };

16:

17: //Constructor của lớp IncrementClass

18: //Bộ khởi tạo với thành viên const

19: IncrementClass::IncrementClass (int C, int I) : Increment(I)

20: {

21: Count = C;

22: }

23:

24: //In dữ liệu

25: void IncrementClass::Print() const

26: {

27: cout << "Count = " << Count

28: #   #   << ", Increment = " << Increment << endl;

30: }

31:

32: int main()

33: {

34: IncrementClass Value(10, 5);

35:

36: cout << "Before incrementing: ";

37: Value.Print();

38: for (int J = 1; J <= 3; J++)

40: {

41: Value.AddIncrement();

42: cout << "After increment " << J << ": ";

43: Value.Print();

44: }

45: return 0;

46: }

Chương trình này sử dụng cú pháp bộ khởi tạo thành viên để khởi tạo thành viên dữ liệu const Increment của lớp IncrementClass ở dòng 19.

Chúng ta chạy ví dụ 3.14, kết quả ở hình 3.14

Hình 14
Hình 14 (graphics14.png)

Hình 3.14: Kết quả của ví dụ 3.14

Ký hiệu : Increment(I) (ở dòng 19 của ví dụ 3.14) sinh ra Increment được khởi động với giá trị là I. Nếu nhiều bộ khởi tạo thành viên được cần, đơn giản bao gồm chúng trong danh sách phân cách dấu phẩy sau dấu hai chấm. Tất cả các thành viên dữ liệu có thể được khởi tạo sử dụng cú pháp bộ khởi tạo thành viên.

Nếu trong ví dụ 3.14 chúng ta cố gắng khởi tạo Increment với một lệnh gán hơn là với một bộ khởi tạo thành viên như sau:

IncrementClass::IncrementClass (int C, int I)

{Count = C;

Increment = I;

}

Khi đó trình biên dịch (Borland C++) sẽ có thông báo lỗi như sau:

Hình 15
Hình 15 (graphics15.png)

Hình 3.15: Thông báo lỗi khi cố gắng khởi tạo một thành viên dữ liệu const bằng phép gán

LỚP NHƯ LÀ CÁC THÀNH VIÊN CỦA CÁC LỚP KHÁC

Một lớp có thể có các đối tượng của các lớp khác như các thành viên. Khi một đối tượng đi vào phạm vi, constructor của nó được gọi một cách tự động, vì thế chúng ta cần mô tả các tham số được truyền như thế nào tới các constructor của đối tượng thành viên. Các đối tượng thành viên được xây dựng theo thứ tự mà trong đó chúng được khai báo (không theo thứ tự mà chúng được liệt kê trong danh sách bộ khởi tạo thành viên của constructor) và trước các đối tượng của lớp chứa đựng chúng được xây dựng.

Ví dụ 3.15: Chương trình sau minh họa các đối tượng như các thành viên của các đối tượng khác.

1: #include <iostream.h>

2: #include <string.h>

3:

4: class Date

5: {

6: public:

7: Date(int = 1, int = 1, int = 1900); //Constructor mặc định

8: void Print() const; //In ngày theo dạng Month/Day/Year

9: private:

10: int Month; //1-12

11: int Day; //1-31

12: int Year; //Năm bất kỳ

13://Hàm tiện ích để kiểm tra Day tương thích đối với Month và Year

14: int CheckDay(int);

15: };

16:

17: class Employee

18: {

19: public:

20: Employee(char *, char *, int, int, int, int, int, int);

21: void Print() const;

22: private:

23: char LastName[25];

24: char FirstName[25];

25: Date BirthDate;

26: Date HireDate;

27: };

28:

29: //Constructor: xác nhận giá trị tương thích của Month

30: //Gọi hàm CheckDay() để xác nhận giá trị tương thích của Day

31: Date::Date(int Mn, int Dy, int Yr)

32: {

33: if (Mn > 0 && Mn <= 12)

34: Month = Mn;

35: else

36: {

37: Month = 1;

38: cout << "Month " << Mn << " invalid. Set to Month 1."

39:            << endl;

40: }

41: Year = Yr;

42: Day = CheckDay(Dy);

43: cout << "Date object constructor for date ";

44: Print();

45: cout << endl;

46: }

47:

48: //Hàm xác nhận giá trị Day tương thích đưa vào Month và Year

49: int Date::CheckDay(int TestDay)

50: {

51: static int DaysPerMonth[13] = {0, 31, 28, 31, 30, 31,

52: 9; 9; 9; 9; 9; 9; #   #   #   #   30, 31, 31, 30,31, 30, 31};

53:

54: if (TestDay > 0 && TestDay <= DaysPerMonth[Month])

55: return TestDay;

56: if (Month == 2 && TestDay == 29 &&

57: ; ; (Year % 400 == 0 || (Year % 4 == 0 && Year % 100 != 0)))

58: return TestDay;

59: cout << "Day " << TestDay << "invalid. Set to Day 1." << endl;

60: return 1;

61: }

62:

63: //In đối tượng Date dạng Month/Day/Year

64: void Date::Print() const

65: {

66: cout << Month << '/' << Day << '/' << Year;

67: }

68:

69: Employee::Employee(char *FName, char *LName,

70:             int BMonth, int BDay, int BYear,

71:             int HMonth, int HDay, int HYear)

72: :BirthDate(BMonth, BDay, BYear), HireDate(HMonth, HDay, HYear)

73: {

74://Sao chép FName vào FirstName và phải chắc chắn rằng nó phù hợp

75: int Length = strlen(FName);

76:

77: Length = Length < 25 ? Length : 24;

78: strncpy(FirstName, FName, Length);

79: FirstName[Length] = '\0';

80: //Sao chép LName vào LastName và phải chắc chắn rằng nó phù hợp

81: Length = strlen(LName);

82: Length = Length < 25 ? Length : 24;

83: strncpy(LastName, LName, 24);

84: LastName[Length] = '\0';

85: cout << "Employee object constructor: "

86:       << FirstName << ' ' << LastName << endl;

87: }

88:

89: void Employee::Print() const

90: {

91: cout << LastName << ", " << FirstName << endl << "Hired: ";

92: HireDate.Print();

93: cout << " Birthday: ";

94: BirthDate.Print();

95: cout << endl;

96: }

97:

98: int main()

99: {

100: Employee E("Bob", "Jones", 7, 24, 49, 3, 12, 88);

101:

102 cout << endl;

103: E.Print();

104: cout << endl << "Test Date constructor with invalid values:"

105:       << endl;

106: Date D(14, 35, 94); //Các giá trị Date không hợp lệ

107: return 0;

108: }

Chương trình gồm lớp Employee chứa các thành viên dữ liệu private LastName, FirstName, BirthDate và HireDate. Các thành viên BirthDate và HireDate là các đối tượng của lớp Date mà chứa các thành viên dữ liệu private Month, Day và Year. Chương trình khởi tạo một đối tượng Employee, và các khởi tạo và các hiển thị các thành viên dữ liệu của nó. Chú ý về cú pháp của phần đầu trong định nghĩa constructor của lớp Employee:

Employee::Employee(char *FName, char *LName, int BMonth, int BDay, int BYear,

int HMonth, int HDay, int HYear)

:BirthDate(BMonth, BDay, BYear), HireDate(HMonth, HDay, HYear)

Constructor lấy tám tham số (FName, LName, BMonth, BDay, BYear, HMonth, HDay, và HYear). Dấu hai chấm trong phần đầu phân tách các bộ khởi tạo từ danh sách các tham số. Các bộ khởi tạo định rõ các tham số truyền chon constructor của các đối tượng thành viên. Vì thế BMonth, BDay và BYear được truyền cho constructor của đối tượng BirthDate, và HMonth, HDay, và HYear được truyền cho constructor của đối tượng HireDate. Nhiều bộ khởi tạo được phân tách bởi dấu phẩy.

Chúng ta chạy ví dụ 3.15, kết quả ở hình 3.16

Hình 16
Hình 16 (graphics16.png)

Hình 3.16: Kết quả của ví dụ 3.15

Một đối tượng thành viên không cần được khởi tạo thông qua một bộ khởi tạo thành viên. Nếu một bộ khởi tạo thành viên không được cung cấp, constructor mặc định của đối tượng thành viên sẽ được gọi một cách tự động. Các giá trị nếu có thiết lập bởi constructor mặc định thì có thể được ghi đè bởi các hàm set.

CÁC HÀM VÀ CÁC LỚP friend

Một hàm friend của một lớp được định nghĩa bên ngoài phạm vi của lớp đó, lúc này có quyền truy cập đến các thành viên private hoặc protected của một lớp. Một hàm hay toàn bộ lớp có thể được khai báo là một friend của lớp khác.

Để khai báo một hàm là một friend của một lớp, đứng trước prototype của hàm trong định nghĩa lớp với từ khóa friend. như sau:

friend <function-declarator>;

Để khai báo một lớp là friend của lớp khác như sau:

friend <class-name>;

Ví dụ 3.16: Chương trình sau minh họa khai báo và sử dụng hàm friend.

1: #include <iostream.h>

2:

3: class Count

4: {

5: friend void SetX(Count &, int); //Khai báo friend

6: public:

7: Count()//Constructor

8: {

9: X = 0;

10: }

11: void Print() const //Xuất

12: {

13: cout << X << endl;

14: }

15: private:

16: int X;

17: };

18:

19: //Có thể thay đổi dữ liệu private của lớp Count vì

20: //SetX() khai báo là một hàm friend của lớp Count

21: void SetX(Count &C, int Val)

22: {

23: C.X = Val; //Hợp lệ: SetX() là một hàm friend của lớp Count

24: }

25:

26: int main()

27: {

28: Count Object;

29:

30: cout << "Object.X after instantiation: ";

31: Object.Print();

32: cout << "Object.X after call to SetX friend function: ";

33: SetX(Object, 8); //Thiết lập X với một friend

34: Object.Print();

35: return 0;

36: }

Chúng ta chạy ví dụ 3.16, kết quả ở hình 3.17

Hình 17
Hình 17 (graphics17.png)

Hình 3.17: Kết quả của ví dụ 3.16

Có thể chỉ định các hàm được đa năng hóa là các friend của lớp. Mỗi hàm được đa năng hóa phải được khai báo tường minh trong định nghĩa lớp như là một friend của lớp.

CON TRỎ THIS

Khi một hàm thành viên tham chiếu thành viên khác của lớp cho đối tượng cụ thể của lớp đó, làm thế nào C++ bảo đảm rằng đối tượng thích hợp được tham chiếu? Câu trả lời là mỗi đối tượng duy trì một con trỏ trỏ tới chính nó – gọi là con trỏ this – Đó là một tham số ẩn trong tất cả các tham chiếu tới các thành viên bên trong đối tượng đó. Con trỏ this cũng có thể được sử dụng tường minh. Mỗi đối tượng có thể xác định địa chỉ của chính mình bằng cách sử dụng từ khóa this.

Con trỏ this được sử dụng để tham chiếu cả các thành viên dữ liệu và hàm thành viên của một đối tượng. Kiểu của con trỏ this phụ thuộc vào kiểu của đối tượng và trong hàm thành viên con trỏ this được sử dụng là khai báo const. Chẳng hạn, một hàm thành viên không hằng của lớp Employee con trỏ this có kiểu là:

Employee * const //Con trỏ hằng trỏ tới đối tượng Employee

Đối với một hàm thành viên hằng của lớp Employee con trỏ this có kiểu là:

const Employee * const //Con trỏ hằng trỏ tới đối tượng Employee mà là một hằng

Ví dụ 3.17: Chương trình sau minh họa sử dụng tường minh của con trỏ this để cho phép một hàm thành viên của lớp Test in dữ liệu X của một đối tượng Test.

1: #include <iostream.h>

2:

3: class Test

4: {

5: public:

6: Test(int = 0); // Constructor mặc định

7: void Print() const;

8: private:

9: int X;

10: };

11:

12: Test::Test(int A)

13: {

14: X = A;

15: }

16:

17: void Test::Print() const

18: {

19: cout << " X = " << X << endl

20:       << " this->X = " << this->X << endl

21:       << "(*this).X = " << (*this).X << endl;

22: }

23:

24: int main()

25: {

26: Test A(12);

27:

28: A.Print();

29: return 0;

30: }

Chúng ta chạy ví dụ 3.17, kết quả ở hình 3.18

Hình 18
Hình 18 (graphics18.png)

Hình 3.18: Kết quả của ví dụ 3.17

Một cách khác sử dụng con trỏ this là cho phép móc vào nhau các lời gọi hàm thành viên.

Ví dụ 3.18: Chương trình sau minh họa trả về một tham chiếu tới một đối tượng Time để cho phép các lời gọi hàm thành viên của lớp Time được móc nối vào nhau.

1: #include <iostream.h>

2:

3: class Time

4: {

5: public:

6: Time(int = 0, int = 0, int = 0); // Constructor mặc định

7: // Các hàm set

8: Time &SetTime(int, int, int); // Thiết lập Hour, Minute va Second

9: Time &SetHour(int); // Thiết lập Hour

10: Time &SetMinute(int); // Thiết lập Minute

11: Time &SetSecond(int); // Thiết lập Second

12: // Các hàm get

13: int GetHour() const; // Trả về Hour

14: int GetMinute() const; // Trả về Minute

15: int GetSecond() const; // Trả về Second

16: // Các hàm in

17: void PrintMilitary() const; // In t.gian theo dạng giờ quân đội

18: void PrintStandard() const; // In thời gian theo dạng giờ chuẩn

19: private:

20: int Hour; // 0 - 23

21: int Minute; // 0 - 59

22: int Second; // 0 - 59

23: };

24:

25: // Constructor khởi động dữ liệu private

26: // Gọi hàm thành viên SetTime() để thiết lập các biến

27: // Các giá trị mặc định là 0

28: Time::Time(int Hr, int Min, int Sec)

29: {

30: SetTime(Hr, Min, Sec);

31: }

32:

33: // Thiết lập các giá trị của Hour, Minute, và Second

34: Time &Time::SetTime(int H, int M, int S)

35: {

36: Hour = (H >= 0 && H < 24) ? H : 0;

37: Minute = (M >= 0 && M < 60) ? M : 0;

38: Second = (S >= 0 && S < 60) ? S : 0;

39: return *this; // Cho phép móc nối

40: }

41:

42: // Thiết lập giá trị của Hour

43: Time &Time::SetHour(int H)

44: {

45: Hour = (H >= 0 && H < 24) ? H : 0;

46: return *this; // Cho phép móc nối

47: }

48:

49: // Thiết lập giá trị của Minute

50: Time &Time::SetMinute(int M)

51: {

52: Minute = (M >= 0 && M < 60) ? M : 0;

53: return *this; // Cho phép móc nối

54: }

55:

56: // Thiết lập giá trị của Second

57: Time &Time::SetSecond(int S)

58: {

59: Second = (S >= 0 && S < 60) ? S : 0;

60: return *this; // Cho phép móc nối

61: }

62:

63: // Lấy giá trị của Hour

64: int Time::GetHour() const

65: {

66: return Hour;

67: }

68:

69: // Lấy giá trị của Minute

70: int Time::GetMinute() const

71: {

72: return Minute;

73: }

74:

75: // Lấy giá trị của Second

76: int Time::GetSecond() const

77: {

78: return Second;

79: }

80:

81: // Hiển thị thời gian dạng giờ quân đội: HH:MM:SS

82: void Time::PrintMilitary() const

83: {

84: cout << (Hour < 10 ? "0" : "") << Hour << ":"

85:       << (Minute < 10 ? "0" : "") << Minute << ":"

86:       << (Second < 10 ? "0" : "") << Second;

87: }

88:

89: // Hiển thị thời gian dạng chuẩn: HH:MM:SS AM (hay PM)

90: void Time::PrintStandard() const

91: {

92: cout << ((Hour == 0 || Hour == 12) ? 12 : Hour % 12) << ":"

93:       << (Minute < 10 ? "0" : "") << Minute << ":"

94:       << (Second < 10 ? "0" : "") << Second

95:       << (Hour < 12 ? " AM" : " PM");

96: }

97:

100: int main()

101: {

102: Time T;

103:

104: // Các lời gọi móc nối vào nhau

105: T.SetHour(18).SetMinute(30).SetSecond(22);

106: cout << "Military time: ";

107: T.PrintMilitary();

108: cout << endl << "Standard time: ";

109: T.PrintStandard();

110: cout << endl << endl << "New standard time: ";

111: // Các lời gọi móc nối vào nhau

112: T.SetTime(20, 20, 20).PrintStandard();

113: cout << endl;

114: return 0;

115: }

Các hàm thành viên SetTime(), SetHour(), SetMinute() và SetSecond() mỗi hàm đều trả về *this với kiểu trả về là Time &. Toán tử chấm liên kết từ trái sang phải, vì vậy biểu thức:

T.SetHour(18).SetMinute(30).SetSecond(22);

Đầu tiên gọi T.SetHour(18) thì trả về một tham chiếu tới đối tượng T là giá trị của lời gọi hàm này. Phần còn lại của biểu thức được hiểu như sau:

T.SetMinute(30).SetSecond(22);

T.SetMinute(30) gọi thực hiện và trả về tương đương của T. Phần còn của biểu thức là:

T.SetSecond(22);

Chúng ta chạy ví dụ 3.18, kết quả ở hình 3.19

Hình 19
Hình 19 (graphics19.png)

Hình 3.19: Kết quả của ví dụ 3.18

CÁC ĐỐI TƯỢNG ĐƯỢC CẤP PHÁT ĐỘNG

Các đối tượng có thể được cấp phát động giống như các dữ liệu khác bằng toán tử new và delete. Chẳng hạn:

Time *TimePtr = new Time(1,20,26);

……………..

delete TimePtr;

Toán tử new tự động gọi hàm constructor ,và toán tử delete tự động gọi destructor.

CÁC THÀNH VIÊN TĨNH CỦA LỚP

Bình thường, mỗi đối tượng của một lớp có bản sao chép của chính nó của tất cả các thành viên dữ liệu của lớp. Trong các trường hợp nhất định chỉ có duy nhất một bản chép thành viên dữ liệu đặc biệt cần phải dùng chung bởi tất cả các đối tượng của một lớp. Một thành viên dữ liệu tĩnh được sử dụng cho những điều đó và các lý do khác. Một thành viên dữ liệu tĩnh biểu diễn thông tin toàn lớp (class-wide). Khai báo một thành viên tĩnh bắt đầu với từ khóa static.

Mặc dù các thành viên dữ liệu tĩnh có thể giống như các biến toàn cục, các thành viên dữ liệu tĩnh có phạm vi lớp. Các thành viên tĩnh có thể là public, private hoặc protected. Các thành viên dữ liệu tĩnh phải được khởi tạo một lần (và chỉ một lần) tại phạm vi file. Các thành viên lớp tĩnh public có thể được truy cập thông qua bất kỳ đối tượng nào của lớp đó, hoặc chúng có thể được truy cập thông qua tên lớp sử dụng toán tử định phạm vi. Các thành viên lớp tĩnh private và protected phải được truy cập thông qua các hàm thành viên public của lớp hoặc thông qua các friend của lớp. Các thành viên lớp tĩnh tồn tại ngay cả khi đối tượng của lớp đó không tồn tại. Để truy cập một thành viên lớp tĩnh public khi các đối tượng của lớp không tồn tại, đơn giản thêm vào đầu tên lớp và toán tử định phạm vi cho thành viên dữ liệu. Để truy cập một thành viên lớp tĩnh private hoặc protected khi các đối tượng của lớp không tồn tại, một hàm thành viên public phải được cung cấp và hàm phải được gọi bởi thêm vào đầu tên của nó với tên lớp và toán tử định phạm vi.

Ví dụ 3.19: Chương trình sau minh họa việc sử dụng thành viên dữ liệu tĩnh private và một hàm thành viên tĩnh public.

1: #include <iostream.h>

2: #include <string.h>

3: #include <assert.h>

4:

5: class Employee

6: {

7: public:

8: Employee(const char*, const char*); // Constructor

9: ~Employee(); // Destructor

10: char *GetFirstName() const; // Trả về first name

11: char *GetLastName() const; // Trả về last name

12: // Hàm thành viên tĩnh

13: static int GetCount(); // Trả về số đối tượng khởi tạo

14: private:

15: char *FirstName;

16: char *LastName;

17: // static data member

18: static int Count; // Số đối tượng khởi tạo

19: };

20:

21: // Khởi tạo thành viên dữ liệu tĩnh

22: int Employee::Count = 0;

23:

24://Định nghĩa hàm thành viên tỉnh mà trả về số đối tượng khởi tạo

25: int Employee::GetCount()

26: {

27: return Count;

28: }

29:

30: // Constructor cấp phát động cho first name và last name

31: Employee::Employee(const char *First, const char *Last)

32: {

33: FirstName = new char[ strlen(First) + 1 ];

34: assert(FirstName != 0); // Bảo đảm vùng nhớ được cấp phát

35: strcpy(FirstName, First);

36: LastName = new char[ strlen(Last) + 1 ];

37: assert(LastName != 0); // Bảo đảm vùng nhớ được cấp phát

38: strcpy(LastName, Last);

39: ++Count; // Tăng số đối tượng lên 1

40: cout << "Employee constructor for " << FirstName

41:       << ' ' << LastName << " called." << endl;

42: }

43:

44: // Destructor giải phóng vùng nhớ đã cấp phát

45: Employee::~Employee()

46: {

47: cout << "~Employee() called for " << FirstName

48:       << ' ' << LastName << endl;

49: delete FirstName;

50: delete LastName;

51: --Count; // Giảm số đối tượng xuống 1

52: }

53:

54: // Trả về first name

55: char *Employee::GetFirstName() const

56: {

57: char *TempPtr = new char[strlen(FirstName) + 1];

58: assert(TempPtr != 0); // Bảo đảm vùng nhớ được cấp phát

59: strcpy(TempPtr, FirstName);

60: return TempPtr;

61: }

62:

63: // Trả về last name

64: char *Employee::GetLastName() const

65: {

66: char *TempPtr = new char[strlen(LastName) + 1];

67: assert(TempPtr != 0); // Bảo đảm vùng nhớ được cấp phát

68: strcpy(TempPtr, LastName);

69: return TempPtr;

70: }

71:

72: int main()

73: {

74: cout << "Number of employees before instantiation is "

75:       << Employee::GetCount() << endl; // Sử dụng tên lớp

76: Employee *E1Ptr = new Employee("Susan", "Baker");

77: Employee *E2Ptr = new Employee("Robert", "Jones");

78: cout << "Number of employees after instantiation is "

79:       << E1Ptr->GetCount() << endl;

80: cout << endl << "Employee 1: "

81:      << E1Ptr->GetFirstName()

82:      << " " << E1Ptr->GetLastName()

83:      << endl << "Employee 2: "

84:      << E2Ptr->GetFirstName()

85:      << " " << E2Ptr->GetLastName() << endl << endl;

86: delete E1Ptr;

87: delete E2Ptr;

88: cout << "Number of employees after deletion is "

89:      << Employee::GetCount() << endl;

90: return 0;

91: }

Thành viên dữ liệu Count được khởi tạo là zero ở phạm vi file với lệnh:

int Employee::Count = 0;

Thành viên dữ liệu Count duy trì số các đối tượng của lớp Employee đã được khởi tạo. Khi đối tượng của lớp Employee tồn tại, thành viên Count có thể được tham chiếu thông qua bất kỳ hàm thành viên nào của một đối tượng Employee – trong ví dụ này, Count được tham chiếu bởi cả constructor lẫn destructor. Khi các đối tượng của lớp Employee không tồn tại, thành viên Count có thể vẫn được tham chiếu nhưng chỉ thông qua một lời gọi hàm thành viên tĩnh public GetCount() như sau:

Employee::GetCount()

Hàm GetCount() được sử dụng để xác định số các đối tượng của Employee khởi tạo hiện hành. Chú ý rằng khi không có các đối tượng trong chương trình, lời gọi hàm Employee::GetCount() được đưa ra. Tuy nhiên khi có các đối tượng khởi động hàm GetCount() có thể được gọi thông qua một trong các đối tượng như sau:

E1Ptr->GetCount()

Trong chương trình ở các dòng 34, 37, 58 và 67 sử dụng hàm assert() (định nghĩa trong assert.h). Hàm này kiểm tra giá trị của biểu thức. Nếu giá trị của biểu thức là 0 (false), hàm assert() in một thông báo lỗi và gọi hàm abort() (định nghĩa trong stdlib.h) để kết thúc chương trình thực thi. Nếu biểu thức có giá trị khác 0 (true) thì chương trình tiếp tục. Điều này rất có ích cho công cụ debug đối với việc kiểm tra nếu một biến có giá trị đúng. Chẳng hạn hàm ở dòng 34 hàm assert() kiểm tra con trỏ FirstName để xác định nếu nó không bằng 0 (null). Nếu điều kiện trong khẳng định (assertion) cho trước là đúng, chương trình tiếp tục mà không ngắt. Nếu điều kiện trong khẳng định cho trước là sai, một thông báo lỗi chứa số dòng, điều kiện được kiểm tra, và tên file trong đó sự khẳng định xuất hiện được in, và chương trình kết thúc. Khi đó lập trình viên có thể tập trung vào vùng này của đoạn mã để tìm lỗi.

Các khẳng định không phải xóa từ chương trình khi debug xong. Khi các khẳng định không còn cần thiết cho mục đích debug trong một chương trình, dòng sau:

#define NDEBUG

được thêm vào ở đầu file chương trình. Điều này phát sinh tiền xử lý bỏ qua tất cả các khẳng định thay thế cho lập trình viên xóa mỗi khẳng định bằng tay.

Chúng ta chạy ví dụ 3.19, kết quả ở hình 3.20

Hình 20
Hình 20 (graphics20.png)

Hình 3.20: Kết quả của ví dụ 3.19

Một hàm thành viên có thể được khai báo là static nếu nó không truy cập đến các thành viên không tĩnh. Không giống như các thành viên không tĩnh, một hàm thành viên tĩnh không có con trỏ this bởi vì các thành viên dữ liệu tĩnh và các hàm thành viên tĩnh tồn tại độc lập với bất kỳ đối tượng nào của lớp.

Chú ý: Hàm thành viên dữ liệu tĩnh không được là const.

BÀI TẬP

graphics21.pngBài 1: Xây dựng lớp Stack, dữ liệu bao gồm đỉnh stack và vùng nhớ của stack. Các thao tác gồm:

graphics22.pngKhởi động stack.

graphics23.pngKiểm tra stack có rỗng không?

graphics24.pngKiểm tra stack có đầy không?

graphics25.pngPush và pop.

graphics26.pngBài 2: Xây dựng lớp hình trụ Cylinder, dữ liệu bao gồm bán kính và chiều cao của hình trụ. Các thao tác gồm hàm tính diện tích toàn phần và thể tích của hình trụ đó.

graphics27.pngBài 3: Hãy xây dựng một lớp Point cho các điểm trong không gian ba chiều (x,y,z). Lớp chứa một constructor mặc định, một hàm Negate() để biến đổi điểm thành đại lượng có dấu âm, một hàm Norm() trả về khoảng cách từ gốc và một hàm Print().

graphics28.pngBài 4: Xây dựng một lớp Matrix cho các ma trận bao gồm một constructor mặc định, hàm xuất ma trận, nhập ma trận từ bàn phím, cộng hai ma trận, trừ hai ma trận và nhân hai ma trận.

graphics29.pngBài 5: Xây dựng một lớp Matrix cho các ma trận vuông bao gồm một constructor mặc định, hàm xuất ma trận, tính định thức và tính ma trận nghịch đảo.

graphics30.pngBài 6: Xây dựng lớp Person để quản lý họ tên, năm sinh, điểm chín môn học của tất cả các học viên của lớp học. Cho biết bao nhiêu học viên trong lớp được phép làm luận văn tốt nghiệp, bao nhiêu học viên thi tốt nghiệp, bao nhiêu người phải thi lại và tên môn thi lại. Tiêu chuẩn để xét:

graphics31.pngLàm luận văn phải có điểm trung bình lớn hơn 7 trong đó không có môn nào dưới 5.

graphics32.pngThi tốt nghiệp khi điểm trung bình không lớn hơn 7 và điểm các môn không dưới 5.

graphics33.pngThi lại có môn dưới 5.

graphics34.pngBài 7: Xây dựng một lớp String. Mỗi đối tượng của lớp String sẽ đại diện một chuỗi ký tự. Các thành viên dữ liệu là chiều dài chuỗi và chuỗi ký tự thực. Ngoài constructor và destructor còn có các phương thức như tạo một chuỗi với chiều dài cho trước, tạo một chuỗi từ một chuỗi đã có.

graphics35.pngBài 8: Xây dựng một lớp Vector để lưu trữ vector gồm các số thực. Các thành viên dữ liệu gồm:

graphics36.pngKích thước vector.

graphics37.pngMột mảng động chứa các thành phần của vector.

Ngoài constructor và destructor, còn có các phương thức tính tích vô hướng của hai vector, tính chuẩn của vector (theo chuẩn bất kỳ nào đó).

Hình 21
Hình 21 (graphics38.png)
Bài 9: Xây dựng lớp Employee gồm họ tên và chứng minh nhân dân. Ngoài constructor còn có phương thức nhập, xuất họ tên và chứng minh nhân dân ra màn hình.graphics39.png

Content actions

Download module as:

PDF | EPUB (?)

What is an EPUB file?

EPUB is an electronic book format that can be read on a variety of mobile devices.

Downloading to a reading device

For detailed instructions on how to download this content's EPUB to your specific device, click the "(?)" link.

| More downloads ...

Add module to:

My Favorites (?)

'My Favorites' is a special kind of lens which you can use to bookmark modules and collections. 'My Favorites' can only be seen by you, and collections saved in 'My Favorites' can remember the last module you were on. You need an account to use 'My Favorites'.

| A lens I own (?)

Definition of a lens

Lenses

A lens is a custom view of the content in the repository. You can think of it as a fancy kind of list that will let you see content through the eyes of organizations and people you trust.

What is in a lens?

Lens makers point to materials (modules and collections), creating a guide that includes their own comments and descriptive tags about the content.

Who can create a lens?

Any individual member, a community, or a respected organization.

What are tags? tag icon

Tags are descriptors added by lens makers to help label content, attaching a vocabulary that is meaningful in the context of the lens.

| External bookmarks