Bài giảng Lập trình hướng đối tượng - Operator Overloading

Giới thiệu

Các toán tử của C++

Lý thuyết về operator overloading

Cú pháp operator overloading

Định nghĩa các toán tử thành viên

Phép gán

Định nghĩa các toán tử toàn cục

 

 

ppt59 trang | Chuyên mục: Lập Trình Hướng Đối Tượng | Chia sẻ: dkS00TYs | Lượt xem: 4116 | Lượt tải: 5download
Tóm tắt nội dung Bài giảng Lập trình hướng đối tượng - Operator Overloading, để xem tài liệu hoàn chỉnh bạn click vào nút "TẢI VỀ" ở trên
khai báo lớp và hàm đó có quyền truy nhập ngang với các phương thức của lớp, hàm đó không phải phương thức của lớp Không cần thêm sửa đổi gì cho định nghĩa của hàm đã được khai báo là friend. Định nghĩa trước của phép cộng vẫn giữ nguyên const MyNumber operator+(const MyNumber& num1, const MyNumber& num2) { MyNumber result(num1.value + num2.value); return result; } Tại sao dùng toán tử toàn cục? Đối với toán tử được khai báo là phương thức của lớp, đối tượng chủ (xác định bởi con trỏ this) luôn được hiểu là toán hạng đầu tiên (trái nhất) của phép toán. Nếu muốn dùng cách này, ta phải được quyền bổ sung phương thức vào định nghĩa của lớp/kiểu của toán hạng trái Không phải lúc nào cũng có thể overload toán tử bằng phương thức phép cộng giữa MyNumber và int cần cả hai cách MyNumber + int	và 	int + MyNumber cout >") được overload tương tự, tuy nhiên, định nghĩa thường phức tạp hơn do có thể phải xử lý input để kiểm tra tính hợp lệ tuỳ theo cách ta quy định như thế nào khi in một đối tượng ra thành một chuỗi ký tự ostream& operatorvalue++; 	// Increment value return *this; 	// Return current MyNumber } const MyNumber MyNumber::operator++(int) { // Postfix MyNumber before(this->value); // Create temporary MyNumber // with current value this->value++; 	// Increment value return before; 	// Return MyNumber before increment } Tham số và kiểu trả về Cũng như khi overload các hàm khác, khi overload một toán tử, ta cũng có nhiều lựa chọn về việc truyền tham số và kiểu trả về chỉ có hạn chế rằng ít nhất một trong các tham số phải thuộc kiểu người dùng tự định nghĩa Ở đây, ta có một số lời khuyên về các lựa chọn Tham số và kiểu trả về Các toán hạng: Nên sử dụng tham chiếu mỗi khi có thể (đặc biệt là khi làm việc với các đối tượng lớn) Luôn luôn sử dụng tham số là hằng tham chiếu khi đối số sẽ không bị sửa đổi bool String::operator==(const String &right) const Đối với các toán tử là phương thức, điều đó có nghĩa ta nên khai báo toán tử là hằng thành viên nếu toán hạng đầu tiên sẽ không bị sửa đổi Phần lớn các toán tử (tính toán và so sánh) không sửa đổi các toán hạng của nó, do đó ta sẽ rất hay dùng đến hằng tham chiếu Tham số và kiểu trả về Giá trị trả về không có hạn chế về kiểu trả về đối với toán tử được overload, nhưng nên cố gắng tuân theo tinh thần của các cài đặt có sẵn của toán tử Ví dụ, các phép so sánh (==, !=…) thường trả về giá trị kiểu bool, nên các phiên bản overload cũng nên trả về bool là tham chiếu (tới đối tượng kết quả hoặc một trong các toán hạng) hay một vùng lưu trữ mới Hằng hay không phải hằng Tham số và kiểu trả về Giá trị trả về … Các toán tử sinh một giá trị mới cần có kết quả trả về là một giá trị (thay vì tham chiếu), và là const (để đảm bảo kết quả đó không thể bị sửa đổi như một l-value) Hầu hết các phép toán số học đều sinh giá trị mới ta đã thấy, các phép tăng sau, giảm sau tuân theo hướng dẫn trên Các toán tử trả về một tham chiếu tới đối tượng ban đầu (đã bị sửa đổi), chẳng hạn phép gán và phép tăng trước, nên trả về tham chiếu không phải là hằng để kết quả có thể được tiếp tục sửa đổi tại các thao tác tiếp theo const MyNumber MyNumber::operator+(const MyNumber& right) const MyNumber& MyNumber::operator+=(const MyNumber& right) Tham số và kiểu trả về Lời khuyên cuối cùng: Xem lại cách ta đã dùng để trả về kết quả của toán tử: Cách trên không sai, nhưng C++ cung cấp một cách hiệu quả hơn const MyNumber MyNumber::operator+(const MyNumber& num) { MyNumber result(this->value + num.value); return result; } Tham số và kiểu trả về Trình tự thực hiện cách cũ: const MyNumber MyNumber::operator+(const MyNumber& num) { MyNumber result(this->value + num.value); return result; } Tham số và kiểu trả về Cú pháp của ví dụ trước tạo một đối tượng tạm thời (temporary object) Khi trình biên dịch gặp đoạn mã này, nó hiểu đối tượng được tạo chỉ nhằm mục đích làm giá trị trả về, nên nó tạo thẳng một đối tượng bên ngoài (để trả về) - bỏ qua việc tạo và huỷ đối tượng bên trong lời gọi hàm Vậy, chỉ có một lời gọi duy nhất đến constructor của MyNumber (không phải copy-constructor) thay vì dãy lời gọi trước Quá trình này được gọi là tối ưu hoá giá trị trả về Ghi nhớ rằng quá trình này không chỉ áp dụng được đối với các toán tử. Ta nên sử dụng mỗi khi tạo một đối tượng chỉ để trả về return MyNumber(this->value + num.value); Phương thức hay hàm toàn cục? Khi lựa chọn overload toán tử tại lớp hoặc tại mức toàn cục, trường hợp nào nên chọn kiểu nào? Một số toán tử phải là thành viên: "=", "[]", "()", và "->", "->*" phải là thành viên Các toán tử đơn nên là thành viên(để đảm bảo tính đóng gói) Khi toán hạng trái có thể được gán trị, toán tử nên là thành viên ("+=", "-=", "/=",…) Mọi toán tử đôi khác không nên là thành viên Trừ khi ta muốn các toán tử này là hàm ảo trong cây thừa kế Phương thức hay hàm toàn cục? Các toán tử là thành viên nên là hằng hàm mỗi khi có thể Điều này cho phép tính mềm dẻo khi làm việc với hằng Nếu ta cảm thấy không nên cho phép sử dụng một toán tử nào đó với lớp của ta (và không muốn các nhà thiết kế khác định nghĩa nó), ta khai báo toán tử đó dạng private (và không cài đặt toán tử đó) Ví du: Kiểu Date Date class Overload phép tăng thay đổi ngày, tháng, năm Overloaded += hàm kiểm tra năm nhuận hàm kiểm tra xem một ngày có phải cuối tháng date1.h (1 of 2) 1 // Fig. 8.10: date1.h 2 // Date class definition. 3 #ifndef DATE1_H 4 #define DATE1_H 5 #include 6 7 using std::ostream; 8 9 class Date { 10 friend ostream &operator 4 #include "date1.h" 5 6 // initialize static member at file scope; 7 // one class-wide copy 8 const int Date::days[] = 9 { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 10 11 // Date constructor 12 Date::Date( int m, int d, int y ) 13 { 14 setDate( m, d, y ); 15 16 } // end Date constructor 17 18 // set month, day and year 19 void Date::setDate( int mm, int dd, int yy ) 20 { 21 month = ( mm >= 1 && mm = 1900 && yy = 1 && dd = 1 && dd 4 5 using std::cout; 6 using std::endl; 7 8 #include "date1.h" // Date class definition 9 10 int main() 11 { 12 Date d1; // defaults to January 1, 1900 13 Date d2( 12, 27, 1992 ); 14 Date d3( 0, 99, 8045 ); // invalid date 15 16 cout << "d1 is " << d1 << "\nd2 is " << d2 17 << "\nd3 is " << d3; 18 19 cout << "\n\nd2 += 7 is " << ( d2 += 7 ); 20 21 d3.setDate( 2, 28, 1992 ); 22 cout << "\n\n d3 is " << d3; 23 cout << "\n++d3 is " << ++d3; 24 25 Date d4( 7, 13, 2002 ); fig08_12.cpp(2 of 2) 26 27 cout << "\n\nTesting the preincrement operator:\n" 28 << " d4 is " << d4 << '\n'; 29 cout << "++d4 is " << ++d4 << '\n'; 30 cout << " d4 is " << d4; 31 32 cout << "\n\nTesting the postincrement operator:\n" 33 << " d4 is " << d4 << '\n'; 34 cout << "d4++ is " << d4++ << '\n'; 35 cout << " d4 is " << d4 << endl; 36 37 return 0; 38 39 } // end main d1 is January 1, 1900 d2 is December 27, 1992 d3 is January 1, 1900 d2 += 7 is January 3, 1993 d3 is February 28, 1992 ++d3 is February 29, 1992 Testing the preincrement operator: d4 is July 13, 2002 ++d4 is July 14, 2002 d4 is July 14, 2002 Testing the postincrement operator: d4 is July 14, 2002 d4++ is July 14, 2002 d4 is July 15, 2002 Đổi kiểu tự độngAutomatic type conversion Ta đã nói về nhiều công việc mà đôi khi C++ ngầm thực hiện Một trong những việc đó là thực hiện đổi kiểu tự động Ví dụ, nếu ta gọi một hàm đòi hỏi một kiểu dữ liệu có sẵn nhưng ta cho hàm một kiểu hơi khác, C++ đôi khi sẽ tự đổi kiểu tham số truyền vào Đối với các lớp dữ liệu người dùng, C++ cũng cung cấp khả năng định nghĩa các phép chuyển đổi tự động void foo(double x); ... foo(2); Đổi kiểu tự động Có hai cách định nghĩa phép đổi kiểu tự động Khai báo và định nghĩa một constructor lấy một tham số duy nhất Khai báo và định nghĩa một toán tử đặc biệt (special overloaded operator) class MyNumber { public: MyNumber(int value = 0); ... class MyOtherNumber { public: MyOtherNumber(…); … operator MyNumber() const; Đổi từ MyOtherNumber sang MyNumber Đổi từ int sang MyNumber Đổi kiểu tự động - constructor Cách 1: định nghĩa một constructor lấy một tham số duy nhất (giá trị hoặc tham chiếu) thuộc một kiểu nào đó, constructor đó sẽ được ngầm gọi khi cần đổi kiểu. class MyNumber { public: MyNumber(int value = 0); ... void foo(MyNumber num) { … } ... int x = 5; foo(x); // Automatically converts the argument ... Đổi kiểu tự động - constructor int x = 5; Foo(x); // This will generate an error Foo(MyNumber(x)); // Explicit conversion - ok Nếu ta muốn ngăn chặn đổi kiểu tự động kiểu này, ta có thể dùng từ khoá explicit khi khai báo constructor Kết quả là việc đổi kiểu chỉ xảy ra khi được gọi một cách tường minh class MyNumber { public: explicit MyNumber(int value = 0); ... Đổi kiểu tự động – toán tử Cách 2: định nghĩa phép đổi kiểu sử dụng một toán tử đặc biệt Tạo một toán tử là thành viên của lớp cần đổi, toán tử này sẽ chuyển đối tượng của lớp này sang kiểu mong muốn Toán tử này hơi đặc biệt: không có kiểu trả về Thực ra, tên của toán tử chính là kiểu trả về Đổi kiểu tự động – toán tử Giả sử cần một phép chuyển đổi tự động từ kiểu MyOtherNumber sang kiểu MyNumber khai báo một toán tử có tên MyNumber : class MyOtherNumber { public: MyOtherNumber(…); … operator MyNumber() const; Thân toán tử chỉ cần tạo và trả về một thể hiện của lớp đích Nên là hàm inline operator MyNumber() const { return MyNumber(…); } Đổi kiểu tự động - đổi kiểu ẩn class Fee { public: Fee(int) {} }; class Fo { int i; public: Fo(int x = 0) : i(x) {} operator Fee() const { return Fee(i); } }; int main() { Fo fo; Fee fee = fo; } ///:~ Đổi kiểu tự động Nói chung, nên tránh đổi kiểu tự động, do chúng có thể dẫn đến các kết quả người dùng không mong đợi Nếu có constructor lấy đúng một tham số khác kiểu lớp chủ, hiện tượng đổi kiểu tự động sẽ xảy ra Chính sách an toàn nhất là khai báo các constructor là explicit, trừ khi kết quả của chúng đúng là cái mà người dùng mong đợi 

File đính kèm:

  • pptBài giảng Lập trình hướng đối tượng - Operator Overloading.ppt