[Học OOP] Bài 7: Overload toán tử trong Lập trình hướng đối tượng c++

Bài viết này là phần 7 trong 9 bài của Series Học lập trình hướng đối tượng OOP

Toán tử là một ký hiệu được sử dụng trong các biểu thức (Ví dụ: + – * / …). Ngôn ngữ lập trình C++ cho phép người lập trình viên Overload toán tử và hàm để phục vụ riêng cho từng loại dữ liệu tự tạo ra.

1. Giới thiệu về Operator Overloading

Operator Overloading hay gọi là nạp chồng toán tử, được dùng để định nghĩa toán tử cho có sẵn trong c++ phục vụ cho dữ liệu riêng do bạn tạo ra.

Giả sử có lớp PhanSo và có các hàm thành phần như Set, Cong, Tru, Nhan, Chia.

Khi bạn muốn tính biểu thức KQ = A+B với những gì đã hướng dẫn trong các bài viết trước thì bạn phải thực hiện như sau:

Ở trên là một biểu thức đơn giản, nếu số lượng phép tính nhiều lên, bạn sẽ rất khó để gọi các hàm và xử lí, dễ gây rối cho người lập trình và thiếu trực quan.

Chính vì vậy trong bài viết này mình sẽ hướng dẫn bạn overloading toán tử để có thể thay  KQ.Set(A.Cong(B)); Bên trên thành  KQ = A + B;

Việc sử dụng toán tử cũng có bản chất như gọi hàm để xử lí dữ liệu.

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

– C++ chỉ cho phép người dùng overloading lại các toán tử có sẵn trong c++

– Một toán tử có thể được định nghĩa cho nhiều kiểu dữ liệu khác nhau.

Ví dụ:

Dù cùng kiểu toán tử + nhưng C++ vẫn cho phép sử dụng cho cả kiểu dữ liệu PhanSo và int.

Các loại toán tử - Overloading operator

Các loại toán tử – Overloading operator

a. Toán tử đơn

Toán tử đơn là toán tử một ngôi (unary operator), có thể được dùng làm toán tử trước (prefix operator) và toán tử sau (postfix operator). Ví dụ phép tăng (++) hay phép giảm (–)

Ví dụ:

  • prefix operator: ++A;
  • postfix operator: A++;

b. Toán tử đôi

Toán tử đôi là toán tử có 2 ngôi (binary operator).

Ví dụ: như A+B, A*B, hay toán tử chỉ mục “[…]” cũng là toán tử đôi.

c. Các toán tử có thể overload

Các loại toán được có thể và không thể overload.

Các loại toán được có thể và không thể overload.

3. Cú pháp Operator Overloading

Dạng toán tửPhương thức của lớpHàm toàn cục
aa@bbaa.operator@(bb)operator@(aa,bb)
@aaaa.operator@()operator@(aa)
aa@aa.operator@(int)operator@(aa,int)

a. khai báo A+B cho class phân số

Chúng ta nhìn vào bảng bên trên, A+B có dạng của aa@bb cho nên nếu bạn khai báo ở phương thức của lớp thì khai báo như sau: aa.operator@(bb)

Ở file PhanSo.h bạn khai báo trước

PhanSo operator+(PhanSo b);

Sau đó viết source cho hàm này:

Khai báo cho hàm toàn cục: có dạng operator@(aa,bb)

Trước tiên bạn cũng khai báo như trên, tuy nhiên hàm toàn cục nên bạn phải thiêt lập hàm bạn để nó có thể truy cập vào các thuộc tính

friend PhanSo operator+(PhanSo a, PhanSo b);

Sau đó viết source cho hàm này:

b. khai báo số đối -A cho class phân số

Ví dụ bạn muốn sử dụng phép toán -a để lấy số đối của PhanSo thì nhìn vào bảng bên trên, -a có dạng @aa

Đối với khai báo ở Phương thức của lớp:

Bạn khai báo theo cú pháp aa.operator@()

PhanSo operator-();

Và viết nội dung cho hàm này

Đối với khai báo ở Hàm toàn cục:

Có dạng operator@(aa)

Đầu tiên bạn khai báo theo cú pháp  friend PhanSo operator-(PhanSo A);

Sau đó viết nội dung cho hàm này ở cục bộ

4. Chuyển kiểu dữ liệu

Xét ví dụ sau:

Để viết class PhanSo cho phép sử dụng các phép tính như:

  • A + B (Với A, B là một PhanSo);
  • A + 5 (Với 5 là một số nguyên)
  • 3 + A

Ít nhất bạn phải khai báo 3 dạng:

Còn trường hợp int + PhanSo (ví dụ: 3+A), Có dạng aa@bb Tuy nhiên aa lúc này là một số nguyên, mà khai báo ở phương thức của lớp thì aa chính là đối tương của lớp ( aa.operator@(bb) ). Nên trong trường hợp này bạn phải khai báo ở hàm toàn cục có dạng operator@(aa,bb)

Tuy nhiên, nếu phép toán 2 ngôi, có cả +, -, *, /, các phép toán so sánh thì mỗi loại bạn phải khai báo 3 lần, rất hỗn loạn, dài và khó kiểm soát, cách viết những hàm tương tự nhau như vậy dễ gây cho bạn sự mệt mỏi và thiếu chính xác. Lúc này việc áp dụng chuyển kiểu dữ liệu trong Overload toán tử sẽ làm một giải pháp hiệu quả.

a. Chuyển kiểu bằng constructor

Theo dõi đoạn code dưới đây

Khi bạn viết PhanSo a=1  thì chương trình sẽ gọi constructor  PhanSo (int t) { Set(t,1);}  Để khỏi tạo đối tượng, một phần vì số 1 là số int nên chương trình chạy constructor đúng đối số này.

Và khi bạn viết PhanSo a = 1 + PhanSo(1,2)  lúc này chương trình sẽ hiểu  PhanSo a = PhanSo(1) + PhanSo(1,2) và bài toán trở về PhanSo + PhanSo.

Đó là cách giải quyết vấn đề bên trên. Cách này giúp bạn tiết kiệm được công sức, và giảm được sai sót, cũng như tổng quát các trường hợp hơn.

b. Chuyển kiểu bằng phép toán chuyển kiểu

Nếu như chuyển kiểu bằng constructor từ kiểu đang có sẵn để phù hợp với dữ liệu của kiểu mới, thì chuyển kiểu bằng phép toán chuyển kiểu cho phép chuyển từ kiểu dữ liệu do mình định nghĩa thành các kiểu có sẵn.

Tóm lại: ví dụ mình có PhanSo a=PhanSo(1,2)  mình có thể lấy số thực của phân số này bằng cách float x = (float)a;

Khai báo

Đó là một ví dụ, bạn có thể thay float bằng các kiểu có sẵn trong c++

 

Ngoài ra bạn nên xem thêm về sự nhập nhằng khi chuyển kiểu

5. Overloading toán tử gán (=)

Khai báo:  MyClass& MyClass::operator=(const MyClass &rhs)

Cú pháp và cách xử lí ở đây khá giống copy constructor.

6. Overloading toán tử nhập xuất cin cout (>> <<)

Việc overloading toán tử nhập xuất cho phép người dùng dùng cin, cout nhập xuất nhanh một đối tượng mà không cần gọi lại cin cout cho từng thuộc tính của dữ liệu dựa trên việc được định nghĩa trước.

Cú pháp tổng quát

Lí do chúng ta sử dụng hàm friendkhông phải hàm của phương thức là vì bên trái toán tử cin rồi đến toán tử >> rồi đến class cần xử lí. Loại này có dạng aa@bb Tuy nhiên aa trong bảng ở mục 3, là đối tượng của class hiện tại, mà cin hay cout là không thuộc class hiện tại, nên không thể khai báo hàm của phương thức. Nên buộc tại đây chúng ta sử dụng friend.

a. Overloading toán tử nhập cin >>

Cú pháp khai báo

Ở đây mình lấy ví dụ là nhập cho một PhanSo.

Khi đó bên trong hàm bạn nhập bằng in >> ….. do đã khai báo istream &in

b. Overloading toán tử xuất cout <<

Cú pháp khai báo

Ở đây mình lấy ví dụ là xuất cho một PhanSo.

Khi đó bên trong hàm bạn xuất bằng out << ….. do đã khai báo ostream &out

Xem thêm: https://kienthuc24h.com/struct-cong-2-phan-can-ban/

7. Bài tập ứng dụng Operator Overloading

Về cơ bản còn nhiều loại toán tử khác như (), [],… tuy nhiên mình chỉ hướng các bạn cách để học, ngoài ra để tìm hiểu kĩ hơn các bạn có thể có thể tìm trên các diễn đàn, website khác cách khai báo và cài đặt các loại toán tử còn lại.

a. Bài tập viết class PhanSo hoàn chỉnh sử dụng Operator Overloading

Đề bài: Viết class thể hiện Phân số, có thể +, -, *, /, các phép so sánh logic và sử dụng toán tử nhập xuất.

b. Code class PhanSo hoàn chỉnh sử dụng Operator Overloading

Phanso.h

Các bạn lưu ý rằng, đối với phân số, việc khai báo ++ hay — như trên là sai ý nghĩa, tuy nhiên mình vẫn viết để các bạn hiểu cách dùng nó như thế nào.

Về ý nghĩa, ++ hay — cho phép nó thay đổi thành số sau nó hoặc trước nó, như ta có số 5, ++ thì sẽ ra 6. Hoặc char ‘B’, ++ thì sẽ ra C. Còn việc dùng ++, — trong phân số là sai ý nghĩa.

PhanSo.cpp

Main.cpp

Nếu có thắc mắc gì vui lòng thảo luận tại đây. Mình sẽ giải đáp cho các bạn.

Chúc các bạn học tốt.

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *