Bài giảng lập trình hướng đối tượng - Thầy Cường Học viện bưu chính viễn thông TP HCM
Chương 5 Quá tải toán tử • Quá tải toán tử đối với lớp • Quá tải toán tử nhò nguyên • Quá tải toán tử quan hệ & luận lý • Quá tải toán tử đơn nguyên • Hàm toán tử friend • Toán tử gán Chương 5 Quá tải toán tử 136 136 Chương 5 Quá tải toán tử 137 137 I/ Quá tải toán tử (operator overloading) Quá tải toán tử giống như quá tải hàm. Thực chất quá tải toán tử chỉ là một loại quá tải hàm. Một toán tử thường được quá tải đối với một lớp. Khi một toán tử được quá tải, toán tử đó không mất ý nghóa gốc của nó. Thêm nữa, toán tử còn có thêm ý nghiã bổ sung đối với lớp mà toán tử được đònh nghiã. • Để quá tải một toán tử, hãy tạo ra một hàm toán tử (operator function). Thông thường, một hàm toán tử là một thành viên (member) hoặc bạn (friend) của lớp mà toán tử được đònh nghiã. Cú pháp return_type class_name::operator#(arg_list) { // operation to be performed } return_type kiểu trả về của một hàm toán tử có thể là bất kỳ, thường là lớp mà toán tử được đònh nghiã class_name tên lớp chứa hàm toán tử # đại diện cho toán tử được quá tải arg_list danh sách các đối số, thay đổi phụ thuộc vào cách mà hàm toán tử được thực hiện và kiểu toán tử được quá tải • Hai hạn chế khi quá tải toán tử : + thứ tụ ưu tiên của các toán tử không thay đổi + số toán hạng của một toán tử không thay đổi Hầu hết các toán tử trong C++ có thể được quá tải. Một số toán tử không thể quá tải như : :: , . , * , ? Cũng không thể quá tải toán tử tiền xử lý. Hai toán tử được quá tải ">>" và "<<" , dùng để thực hiện các thao tác nhập/xuất trong C++ • Các hàm toán tử có thể không có các đối số ngầm đònh. Chương 5 Quá tải toán tử 138 138 II/ Quá tải toán tử nhò nguyên Khi một hàm toán tử thành viên quá tải toán tử nhò nguyên, hàm sẽ chỉ có một tham số. Tham số này sẽ nhận đối tượng nằm bên phải toán tử. Đối tượng bên trái là đối tượng tạo ra lời gọi. Gọi cho hàm toán tử và được truyền bởi con trỏ this . Các hàm toán tử được viết theo nhiều cách khác nhau. • Quá tải toán tử "+" đối với một lớp Ví dụ 2.1 // Overload the + relative to coord class. #include <iostream.h> class coord { int x, y; // coordinate values public: coord() { x=0; y=0; } coord(int i, int j) { x=i; y=j; } void get_xy(int &i, int &j) { i=x; j=y; } coord operator+(coord ob2); }; // Overload + relative to coord class. coord coord::operator+(coord ob2) { coord temp; temp.x = x + ob2.x; temp.y = y + ob2.y; return temp; } int main() { coord o1(10, 10), o2(5, 3), o3; int x, y; Chương 5 Quá tải toán tử 139 139 o3 = o1 + o2; // add two objects - this calls operator+() o3.get_xy(x, y); cout << "(o1+o2) X: " << x << ", Y: " << y << "\n"; return 0; } @ Khi trả về một đối tượng, toán tử phép "+" cho phép một chuổi phép cộng, ví dụ o3 = o1 + o2 + o1 + o3; @ Vì đối tượng coord được trả về, câu lệnh sau đây cũng hoàn toàn đúng (o1 + o2).get_xy(x, y); • Quá tải toán tử "-" , "=" đối với một lớp Ví dụ 2.2 // Overload the +, -, and = relative to coord class. #include <iostream.h> class coord { int x, y; // coordinate values public: coord() { x=0; y=0; } coord(int i, int j) { x=i; y=j; } void get_xy(int &i, int &j) { i=x; j=y; } coord operator+(coord ob2); coord operator-(coord ob2); coord operator=(coord ob2); }; // Overload + relative to coord class. coord coord::operator+(coord ob2) { coord temp; temp.x = x + ob2.x; Chương 5 Quá tải toán tử 140 140 temp.y = y + ob2.y; return temp; } // Overload - relative to coord class. coord coord::operator-(coord ob2) { coord temp; temp.x = x - ob2.x; temp.y = y - ob2.y; return temp; } // Overload = relative to coord. coord coord::operator=(coord ob2) { x = ob2.x; y = ob2.y; return *this; // return the object that is assigned } int main() { coord o1(10, 10), o2(5, 3), o3; int x, y; o3 = o1 + o2; // add two objects - this calls operator+() o3.get_xy(x, y); cout << "(o1+o2) X: " << x << ", Y: " << y << "\n"; o3 = o1 - o2; // subtract two objects o3.get_xy(x, y); cout << "(o1-o2) X: " << x << ", Y: " << y << "\n"; o3 = o1; // assign an object Chương 5 Quá tải toán tử 141 141 o3.get_xy(x, y); cout << "(o3=o1) X: " << x << ", Y: " << y << "\n"; return 0; } @ Với hàm operator-() thứ tự của các toán hạng là quan trọng. Do A-B sẽ khác B-A . Vì chính toán hạng bên trái tạo ra lời gọi đối với operator-() nên phép trừ phải theo thứ tự : x - ob2.x ; @ Với hàm toán tử gán + toán hạng bên trái (nghiã là đối tượng được gán cho một giá trò) được thay đổi bởi phép toán. + hàm operator=() trả về con trỏ this, nghiã là hàm trả về đối tượng sẽ được gán. Do đó câu lệnh sau cũng hoàn toàn đúng : o3 = o2 = o1; • Có thể quá tải một toán tử đối với một lớp để cho toán hạng bên phải là một đối tượng có kiểu đònh sẵn. Toán tử "+" được quá tải để cộng một giá trò nguyên và đối tượng coord. Ví dụ 2.3 // Overload + for ob + int as well as ob + ob. #include <iostream.h> class coord { int x, y; // coordinate values public: coord() { x=0; y=0; } coord(int i, int j) { x=i; y=j; } void get_xy(int &i, int &j) { i=x; j=y; } coord operator+(coord ob2); // ob + ob coord operator+(int i); // ob + int }; // Overload + relative to coord class. Chương 5 Quá tải toán tử 142 142 coord coord::operator+(coord ob2) { coord temp; temp.x = x + ob2.x; temp.y = y + ob2.y; return temp; } // Overload + for ob + int coord coord::operator+(int i) { coord temp; temp.x = x + i; temp.y = y + i; return temp; } int main() { coord o1(10, 10), o2(5, 3), o3; int x, y; o3 = o1 + o2; // add two objects - this calls operator+(coord) o3.get_xy(x, y); cout << "(o1+o2) X: " << x << ", Y: " << y << "\n"; o3 = o1 + 100; // add object + int - this call operator+(int) o3.get_xy(x, y); cout << "(o1+100) X: " << x << ", Y: " << y << "\n"; return 0; } Chương 5 Quá tải toán tử 143 143 @ Cần nhớ, khi quá tải một hàm toán tử thành viên để cho một đối tượng có thể được dùng trong một phép toán có kiểu đònh sẵn, thì kiểu đònh sẵn phải ở bên phải của toán tử. Bởi vì chính đối tượng bên trái tạo ra lời gọi cho hàm toán tử. Điều gì xảy ra cho câu lệnh này ? o3 = 100 + o1; // int + ob • Có thể dùng tham số qui chiếu trong một hàm toán tử Ví dụ 2.4 // Overload + relative to coord class using references. coord coord::operator+(coord &ob2) { coord temp; temp.x = x + ob2.x; temp.y = y + ob2.y; return temp; } @ Các lý do phải dùng tham số qui chiếu trong hàm toán tử : + Sự hiệu quả. Do việc truyền điạ chỉ của một đối tượng thường nhanh hơn và cải thiện năng xuất so với truyền các đối tượng như các tham số cho hàm. + Tránh những rắc rối gây ra khi bản sao một toán hạng bò hủy. Tuy nhiên, có thể đònh nghiã một hàm hủy bản sao để ngăn ngừa vấn đề này trong trường hợp tổng quát. Bài tập II 1. Hãy tạo quá tải toán tử "*" và "/" đối với lớp coord. Viết chương trình. Chương 5 Quá tải toán tử 144 144 2. Tại sao phần dưới đây là cách sử dụng không thích hợp của một toán tử được quá tải. coord coord::operator%(coord ob) { double i; cout << "Enter a number: "; cin >> i; cout << "root of " << i << " is "; cout << sqr(i); } 3. Hãy thử thay đổi các kiểu trả về của các hàm toán tử đối với lớp khác lớp coord. Xem kiểu gì có kết qủa sai ? III/ Quá tải các toán tử quan hệ và luận lý Khi quá tải các toán tử quan hệ và luận lý để chúng hoạt động theo cách truyền thống, sẽ không cần các hàm toán tử trả về một đối tượng của lớp, thay vào đó các hàm toán tử trả về một số nguyên để chỉ đúng hay sai. • Quá tải các toán tử "==" và "&&" Ví dụ 3.1 // Overload the == and && relative to coord class. #include <iostream.h> class coord { int x, y; // coordinate values public: coord() { x=0; y=0; } coord(int i, int j) { x=i; y=j; } void get_xy(int &i, int &j) { i=x; j=y; } int operator==(coord ob2); [...]... operator==(coord ob2) ; Chương 5 Quá tải toán tử 146 146 IV/ Quá tải toán tử đơn nguyên Quá tải toán tử đơn nguyên tương tự như toán tử nhị nguyên ngoại trừ chỉ có một toán hạng . Khi quá tải toán tử đơn nguyên bằng cách dùng hàm thành viên, thì hàm không có tham số . Do chỉ có một toán hạng, nên chính toán hạng này tạo ra lời gọi cho hàm toán tử. • Quá tải toán tử tăng "++"... Chương 5 Quá tải toán tử 138 138 II/ Quá tải toán tử nhị nguyên Khi một hàm toán tử thành viên quá tải toán tử nhị nguyên, hàm sẽ chỉ có một tham số. Tham số này sẽ nhận đối tượng nằm bên phải toán tử. Đối tượng bên trái là đối tượng tạo ra lời gọi. Gọi cho hàm toán tử và được truyền bởi con trỏ this . Các hàm toán tử được viết theo nhiều cách khác nhau. • Quá tải toán tử "+"... hạng . Với các toán tử đơn nguyên, hàm toán tử friend được truyền một toán hạng . Không thể dùng hàm friend để quá tải toán tử gán. • Quá tải toán tử "+" bằng cách dùng hàm friend Chương 5 Quá tải toán tử 151 151 @ Toán hạng bên trái được truyền cho tham số thứ nhất, toán hạng bên phải được truyền cho tham số thứ hai. • Quá tải một toán tử bằng cách dùng... trả về giá trị của chuổi s 7. Hãy bổ sung các hàm toán tử cần thiết vào chương trình sau. * Toán tử quá tải + cộng mỗi phần tử của toán hạng. * Toán tử quá tải - trừ phần tử toán hạng bên trái với mỗi phần tử của toán hạng bên phải. * Toán tử quá tải == trả về giá trị đúng nếu mỗi phần tử của mỗi toán hạng là giống nhau và trả về giá trị sai nếu ngược laïi. #include <iostream> class... cho nó là toán tử nhị nguyên lẫn toán tử đơn nguyên. Viết chương trình cho cả hai dạng, khi được dùng như toán tử đơn nguyên hãy thực hiện một giá trị toạ độ âm bất kỳ thành dương. V/ Hàm toán tử friend Có thể quá tải một toán tử đối với lớp bằng cách dùng hàm friend, nên nhớ hàm friend không có con trỏ this. Với các toán tử nhị nguyên, hàm toán tử friend được truyền cả hai toán hạng ....Chương 5 Quá tải toán tử 159 159 4. Viết chương trình thực hiện quá tải toán tử + đối với lớp three_d để cho nó nhận các kiểu phép toán sau : ob + double; double + ob; 5. Viết chương trình thực hiện quá tải các toán tử ==, !=, và // đối với lớp three_d. 6. Viết chương trình tạo lớp strtype để cho phép các kiểu toán tử sau : * ghép chuổi bằng cách dùng toán tử + * gán chuổi... lớp khác lớp coord. Xem kiểu gì có kết qủa sai ? III/ Quá tải các toán tử quan hệ và luận lý Khi quá tải các toán tử quan hệ và luận lý để chúng hoạt động theo cách truyền thống, sẽ không cần các hàm toán tử trả về một đối tượng của lớp, thay vào đó các hàm toán tử trả về một số nguyên để chỉ đúng hay sai . • Quá tải các toán tử "==" và "&&" Ví dụ 3.1 //... 5 Quá tải toán tử 143 143 @ Cần nhớ, khi quá tải một hàm toán tử thành viên để cho một đối tượng có thể được dùng trong một phép toán có kiểu định sẵn, thì kiểu định sẵn phải ở bên phải của toán tử. Bởi vì chính đối tượng bên trái tạo ra lời gọi cho hàm toán tử. Điều gì xảy ra cho câu lệnh này ? o3 = 100 + o1; // int + ob • Có thể dùng tham số qui chiếu trong một hàm toán tử. .. Với hàm toán tử friend, có thể để cho những đối tượng được sử dụng trong các phép toán có các kiểu định sẵn, ở đó kiểu định sẵn nằm ở bên trái toán tử . Trong ví dụ 2.3, chương 5, hàm toán tử thành viên được quá tải coord coord::operator+(int i) câu lệnh ob1 = ob2 + 100; là đúng còn câu lệnh ob1 = 100 + ob2; là sai Với hàm toán tử friend, có thể định nghiã hàm quá tải sao cho toán hạng... y=0; } Chương 5 Quá tải toán tử 149 149 o1 = - o1 ; // negation o1.get_xy(x, y); cout << "(-o1) X: " << x << ", Y: " << y << "\n"; return 0; } Bài tập IV 1. Hãy quá tải toán tử " " đối với lớp coord. Viết chương trình cho cả hai dạng đứng trước và đứng sau. 2. Hãy quá tải toán tử "+" đối . 5 Quá tải toán tử • Quá tải toán tử đối với lớp • Quá tải toán tử nhò nguyên • Quá tải toán tử quan hệ & luận lý • Quá tải toán tử đơn. 137 I/ Quá tải toán tử (operator overloading) Quá tải toán tử giống như quá tải hàm. Thực chất quá tải toán tử chỉ là một loại quá tải hàm. Một toán tử thường