24/05/2018, 18:16

Lớp và đối tượng-trả về một tham chiếu

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

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.

CT3_11.CPP1: #include <iostream.h>2: 3: class Time4: {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 private17: //Gọi hàm thành viên SetTime()để thiết lập các biến18: //Các giá trị mặcđịnh là 019: 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à Second24: 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 Hour32: 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 private39: 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 lvalue55: T.BadSetHour(12) = 74;56: cout << endl << "*********************************" << endl57:       << "BAD PROGRAMMING PRACTICE!!!!!!!!!" << endl58:       << "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 3.11: Kết quả của ví dụ 3.11

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

CT3_12.CPP1: #include <iostream.h>2: 3: //Lớp Date đơn giản4: class Date5: {6: public:7: Date(int = 1, int = 1, int = 1990); //Constructor mặc định8: 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ền16: 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-yyyy24: 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/9032: 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 định37: cout << endl << endl38:       << "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 3.12: Kết quả của ví dụ 3.12

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.

 

CT3_13.CPP1: #include <iostream.h>2: 3: class Time4: {5: public:6: Time(int = 0, int = 0, int = 0); //Constructor mặc định7: //Các hàm set8: void SetTime(int, int, int); //Thiết lập thời gian9: void SetHour(int); //Thiết lập Hour10: void SetMinute(int); //Thiết lập Minute11: void SetSecond(int); //Thiết lập Second12: //Các hàm get13: int GetHour() const; //Trả về Hour14: int GetMinute() const; //Trả về Minute15: int GetSecond() const; //Trả về Second16: //Các hàm in17: void PrintMilitary() const; //In thời gian theo dạng giờ quân đội18: void PrintStandard() const; //In thời gian theo dạng giờ chuẩn19: private:20: int Hour; //0 - 2321: int Minute; //0 - 5922: int Second; //0 – 5923: };24:25: //Constructor khởi động dữ liệu private26: //Các giá trị mặc định là 027: 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à Second33: 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 Hour41: 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 Minute47: 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 Second53: void Time::SetSecond(int s)54: {55: Second = (s >= 0 && s < 60) ? s : 0;56: }57: 58: //Lấy giá trị của Hour59: int Time::GetHour() const60: {61: return Hour;62: }63:64: //Lấy giá trị của Minute65: int Time::GetMinute() const66: {67: return Minute;68: }69: 70: //Lấy giá trị của Second71: int Time::GetSecond() const72: {73: return Second;74: }75:76: //Hiển thị thời gian dạng giờ quân đội: HH:MM:SS77: void Time::PrintMilitary() const78: {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() const86: {87: cout << ((Hour == 12) ? 12 : Hour % 12) << ":"88:       << (Minute < 10 ? "0" : "") << Minute << ":"89:       << (Second < 10 ? "0" : "") << Second90:       << (Hour < 12 ? " AM" : " PM");91: }92:93: int main()94: {95: const Time T(19, 33, 52); //Đối tượng hằng96:97: T.SetHour(12); //ERROR: non-const member function98: T.SetMinute(20); //ERROR: non-const member function99: T.SetSecond(39); //ERROR: non-const member function100: return 0;101: }

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 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: Chương 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.

CT3_14.CPP1: #include <iostream.h>2: 3: class IncrementClass4: {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 const15: };16:17: //Constructor của lớp IncrementClass18: //Bộ khởi tạo với thành viên const19: IncrementClass::IncrementClass (int C, int I) : Increment(I) 20: {21: Count = C;22: }23:24: //In dữ liệu25: void IncrementClass::Print() const26: {27: cout << "Count = " << Count28: #   #   << ", 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 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 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

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.

CT3_15.CPP1: #include <iostream.h>2: #include <string.h>3:4: class Date5: {6: public:7: Date(int = 1, int = 1, int = 1900); //Constructor mặc định8: void Print() const; //In ngày theo dạng Month/Day/Year9: private:10: int Month; //1-1211: int Day; //1-3112: 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à Year14: int CheckDay(int);15: };16:17: class Employee18: {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 Month30: //Gọi hàm CheckDay() để xác nhận giá trị tương thích của Day31: Date::Date(int Mn, int Dy, int Yr)32: {33: if (Mn > 0 && Mn <= 12)34: Month = Mn;35: else36: {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 tiện ích để xác nhận giá trị Day tương thích đưa vào Month và Year49: 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/Year64: void Date::Print() const65: {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ợp75: int Length = strlen(FName);76:77: Length = Length < 25 ? Length : 24;78: strncpy(FirstName, FName, Length);79: FirstName[Length] = '';80: //Sao chép LName vào LastName và phải chắc chắn rằng nó phù hợp81: Length = strlen(LName);82: Length = Length < 25 ? Length : 24;83: strncpy(LastName, LName, 24);84: LastName[Length] = '';85: cout << "Employee object constructor: "86:       << FirstName << ' ' << LastName << endl;87: }88:89: void Employee::Print() const90: {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 privateLastName, FirstName, BirthDateHireDate. Các thành viên BirthDateHireDate 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 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 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 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.

0