C# - C Sharp: Nạp chồng toán hạng

Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực

Điểm nổi bật của nạp chồng toán hạng là không phải lúc nào bạn cũng muốn gọi các phương thức hay thuộc tính trên các thể hiện lớp. Chúng ta thường cần làm một số công việc như cộng các số lượng với nhau, nhân chúng hay thực hiện một số toán hạn logic như so sánh các đối tượng. Ví dụ ta định nghĩa một lớp mô tả ma trận toán học. Các ma trận thì có thể cộng, nhân với nhau như các số, nên ta có thể viết đoạn mã như sau:

Matrix a, b, c;
//chẳng hạn a, b, c đã được khởi tạo
Matrix d = c * (a + b);

Bằng nạp chồng các toán hạng ta có thể làm cho trình biên dịch biết những gì mà + và * làm đối với một ma trận, và bạn có thể viết đoạn mã như trên. Nếu như không sử dụng toán hạng nạp chồng như trên, ta cũng có thể định nghĩa các phương thức để thực hiện các toán hạng trên nhưng nó sẽ có rất nhiều hỗn độn:

Matrix d = c.Multiply(a.Add(b));

Các toán hạng như + và * rất khắc khe với các kiểu dữ liệu định nghĩa trước, và do đó trình biên dịch sẽ tự động biết ý nghĩa của các toán hạng dựa trên các kiểu dữ liệu đó. Ví dụ như nó biết cách để cộng hai số kiểu long, hay cách để chia một số kiểu double cho một số kiểu double. Khi chúng ta định nghĩa lớp hay struct chúng ta phải nói với trình biên dịch mọi thứ như: những phương thức nào có thể được gọi, những trường nào được lưu trữ với mọi thực thể và vân vân. Nếu chúng ta sử dụng các toán hạng như +, * trong lớp của chúng ta. Chúng ta phải nói với trình biên dịch biết ý nghĩa của những toán hạng có liên quan trong ngữ cảnh của lớp đó. Và cách chúng ta làm là định nghĩa nạp chồng cho các toán hạng.

Một số trường hợp chúng ta nên viết các toán hạng nạp chồng:

1. Trong thế giới toán học, mọi đối tượng toán học như: tọa độ, vector, ma trận, hàm số và vân vân. Nếu bạn viết chương trình làm những mô hình toán học hay vật lý, bạn nhất định sẽ mô tả những đối tượng này.

2.Những chương trình đồ hoạ sẽ sử dụng các đối tượng toán học và toạ độ khi tính toán vị trí của trên màn hình.

3. Một lớp mô tả số lượng tiền.

4. Việc sử lý từ hay chương trình phân tích văn bản có lớp để mô tả các câu văn, mệnh đề và bạn phải sử dụng các toán hạng để liên kết các câu lại với nhau.

Cách hoạt động của các toán hạng :

 Để hiểu cách nạp chồng toán hạng, chúng ta phải nghĩ về những gì xảy ra khi trình biên dịch gặp một toán hạng - :

int a = 3;
uint b = 2;
double d = 4.0;
long l = a + b;
double x = d + a; 

Xem dòng lênh:

long l = a + b;

Việc thực hiện a+b như trên là rất trực quan, đó là một cú pháp tiện lợi để nói rằng chúng ta đang gọi phương thức cộng hai số.

Trình biên dịch sẽ thấy nó cần thiết để cộng hai số nguyên và trả về số kiểu long. Ta thấy đây là phép cộng hai số kiểu integer và kết quả cũng là một số integer nhưng nó ép kiểu sang kiểu long và điều này thì cho phép trong C#.

Xét dòng lệnh:

double x = d + a;

Ta thấy trong nạp chồng này có số kiểu double và kiểu integer, cộng chúng lại và trả về kiểu doube. Chúng ta cần phải đổi kiểu int sang kiểu double sau đó cộng hai số đó lại với nhau. Và chúng ta nhận ra sự nạp chồng của toán tử cộng ở đây như là một phiên bản của toán tử nhận hai số double như hai tham số. Và trình biên dịch phải chắc là nó có thể ép kiểu kết quả về một kiểu thích hợp nếu cần.

Xét đoạn mã sau:

Vector vect1, vect2, vect3;
// initialise vect1 and vect2
vect3 = vect1 + vect2;
vect1 = vect1*2;
 

Ở đây vector là một struct, trình biên dịch cần phải cộng hai vector  vect1 và vect2 với nhau. Và nó sẽ tìm một nạp chồng của toán hạng + lấy hai vector như tham số của nó. Và toán hạng này trả về một vector khác. Bởi vậy trình biên dịch cần tìm một định nghĩa của toán hạng có dạng như sau:

public static Vector operator + (Vector lhs, Vector rhs)

Nếu tìm ra nó sẽ thực thi toàn hạng đó. Nếu không nó sẽ sử dụng bất kỳ nạp chồng của toán hạng + nào có hai tham số kiểu dữ liệu khác và có thể chuyển sang thực thể vector. Nếu không tìm được cái nào thích hợp thì nó sẽ báo lỗi.

Ví dụ về nạp chồng toán hạng : struct Vector

Chúng ta sẽ định nghĩa một struct Vector, nó mô tả một vector ba chiều.

Một vector ba chiều là một tập hợp ba con số kiểu double. Các biến mô tả các con số được gọi là x, y, z. Liên kết ba con số lại với nhau và để chúng tạo thành một vector toán học.

Sau đây là định nghĩa cho Vector- chứa các trường thành viên, contructor, và một phương thức ToString() overriden, vì thế chúng ta có thể dễ dàng thấy nội dung của một vector và cuối cùng là nạp chồng toán hạn:

namespace Wrox.ProCSharp.OOCSharp
{
   struct Vector
   {
      public double x, y, z;

      public Vector(double x, double y, double z)
      {
         this.x = x;
         this.y = y;
         this.z = z;
      }

      public Vector(Vector rhs)
      {
         x = rhs.x;
         y = rhs.y;
         z = rhs.z;
      }

      public override string ToString()
      {
         return "( " + x + " , " + y + " , " + z + " )";
      }

Chú ý rằng để làm cho mọi thứ đơn giản thì mọi trường nên được khai báo public. Nên nhớ các struct không cho phép để contructor mặc định. Do đó, tôi đã tạo hai constructor để khởi tạo giá trị cho vector bằng cách truyền giá trị cho mọi phần tử hay truyền một vector khác để sao chép các giá trị của vector này.

Bây giờ hãy xem qua một nạp chồng toán hạng:

      public static Vector operator + (Vector lhs, Vector rhs)
      {
         Vector result = new Vector(lhs);
         result.x += rhs.x;
         result.y += rhs.y;
         result.z += rhs.z;
         return result;
      }
   }
}

Nó làm việc như thế nào? Cú pháp quan trọng là trong khai báo của toán hạng. Nó được khai báo như cách khai báo một phương thức, ngoại trừ từ khoá operation sẽ nói với trình biên dịch là nó là một nạp chồng toán hạng. Toán hạng được đại diện bởi ký kiệu thực tế cho phù hợp. Kiểu trả về là kiểu mà bạn nhận được khi sử dụng toán hạng này. Trong trường hợp của chúng ta, cộng hai vector sẽ cho chúng ta một vector khác, vì thế kiểu trả về là một vector. Bạn có thể override toán hạng + này, bằng cách định nghĩa toán hạng cùng tên nhưng khác kiểu trả về. Hai tham số ở đây để chỉ hai phần tử bạn đang muốn thực hiện toán hạng giữa chúng. Ví dụ như một toán hạng có hai tham số giống như "+" ở trên, tham số đầu là một đối tượng hay giá trị nằm ở phía bên trái dấu "+", và tham số thứ hai là đối tượng hay giá trị nằm bên phải dấu "+".

Cuối cùng, chú ý toán hạng được khai báo static, với ý nghĩa là nó được kết nối với struct hoặc class, không phải với bất kỳ đối tượng nào, và vì thế không truy cập đến một con trỏ this được.

Bây giờ chúng ta đề cập đến cú pháp cho việc khai báo toán hạng +, chúng ta có thể quan sát những gì xảy ra bên trong toán hạng

      {
         Vector result = new Vector(lhs);
         result.x += rhs.x;
         result.y += rhs.y;
         result.z += rhs.z;
         return result;
      }

Phần mã này rõ ràng giống như nếu chúng ta khai báo một phương thức, và bạn có thể tin tưởng rằng nó sẽ trả về một vector chứa tổng của lhs và rhs như định nghĩa ở trên. Chúng ta chỉ đơn giản cộng các số x, y, và z một cách riêng lẽ.

Bây giờ chúng ta cần kiểm tra struct của chúng ta bằng đoạn mã sau:

      static void Main()
      {
         Vector vect1, vect2, vect3;
         vect1 = new Vector(3.0, 3.0, 1.0);
         vect2 = new Vector(2.0, -4.0, -4.0);
         vect3 = vect1 + vect2;
         Console.WriteLine("vect1 = " + vect1.ToString());
         Console.WriteLine("vect2 = " + vect2.ToString());
         Console.WriteLine("vect3 = " + vect3.ToString());
      }

Thực thi ta được kết quả sau:

Vectors
vect1 = ( 3 , 3 , 1 )
vect2 = ( 2 , -4 , -4 )
vect3 = ( 5 , -1 , -3 )

Thêm nhiều sự nạp chồng:

Mặc dù chúng ta hiểu được cách để nạp chồng một toán hạng nhưng trong thực tế ta có rất nhiều toán hạng. Ví dụ như một vector bạn có thể thực hiện cộng hai vector, nhân hai vector, trừ hai vector hoặc so sánh giá trị của nó. Ta sẽ xét ví dụ nạp chồng phép nhân vector bằng cách nhân vô hướng hai vector với nhau.

      public static Vector operator * (double lhs, Vector rhs)
      {
         return new Vector(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z);
      }

Nếu a và b được khai báo kiểu vector thì nó cho phép ta viết mã như sau:

b=2*a;

nhưng nó không cho phép viết như sau:

b=a*2;

Bởi vì lúc này trình biên dich xem như toán hạng nạp chồng giống như phương thức nạp chồng. Nó kiểm tra tất cả các nạp chồng khả thể của toán hạng để tìm được sự ăn khớp tốt nhất. Như ở trên thì nó yêu cầu có tham số đầu tiên là một vector, tham số thứ hai là một số nguyên. Và tìm không thấy nó sẽ báo lỗi. Có 2 cách để xử lý trường hợp trên bằng cách nạp chồng các toán hạng * khác:

      public static Vector operator * (Vector lhs, double rhs)
      {
         return new Vector(rhs * lhs.x, rhs * lhs.y, rhs *l hs.z);
      }

Hoặc:

      public static Vector operator * (Vector lhs, double rhs)
      {
         return rhs * lhs;
      }
 

Nạp chồng các toán hạng so sánh:

Có sáu toán hạng trong C#, và chúng ta xét từng cặp:

  • = = and !=

  • > and <

  • >= and <=

Ý nghĩa của từng cặp này là gấp đôi. Giữa các cặp này luôn luôn có kết quả đối nghịch nhau: Nếu toán hạng đầu trả về giá trị true thì toán hạng kia trả về giá trị false. C# luôn luôn yêu cầu bạn nạp chồng cả hai toán tử đó. Nếu bạn nạp chồng toán tử "= =" thì phải nạp chồng toán tư"!=" nếu không trình biên dịch sẽ báo lỗi.

Có một hạn chế là toán hạng so sánh phải trả về kiểu bool. và đó cũng là điểm khác nhau giữa các toán hạng này và toán hạng số học.

Xét ví dụ ta override toán hạng = = và != cho lớp vector:

      public static bool operator = = (Vector lhs, Vector rhs) 
      {
         if (lhs.x = = rhs.x && lhs.y = = rhs.y && lhs.z = = rhs.z)
            return true;
         else
            return false;
      }

Các vector so sánh được xét bằng nhau trên các giá trị của các thành phần. Đều cần chú ý là bạn đang xét sự bằng nhau giữa hai vector theo sự tham khảo hay theo giá trị của nó.

Và chúng ta cũng làm tương tự cho toán hạng !=:

      public static bool operator != (Vector lhs, Vector rhs)
      {
          return ! (lhs = = rhs);
      }

Ta có đoạn chương trình sau:

  static void Main()
      {
         Vector vect1, vect2, vect3;
         vect1 = new Vector(3.0, 3.0, -10.0);
         vect2 = new Vector(3.0, 3.0, -10.0);
         vect3 = new Vector(2.0, 3.0, 6.0);
         Console.WriteLine("vect1= =vect2 returns  " + (vect1= =vect2));
         Console.WriteLine("vect1= =vect3 returns  " + (vect1= =vect3));
         Console.WriteLine("vect2= =vect3 returns  " + (vect2= =vect3));
         Console.WriteLine();
         Console.WriteLine("vect1!=vect2 returns  " + (vect1!=vect2));
         Console.WriteLine("vect1!=vect3 returns  " + (vect1!=vect3));
         Console.WriteLine("vect2!=vect3 returns  " + (vect2!=vect3));
      }

Khi chạy và biên dịch, trình biên dịch sẽ cảnh báo bạn không override phương thức Equals() cho vector. Nhưng với mục đích của chúng ta thì nó không có ý nghĩa gì:

Vectors3
vect1= =vect2 returns  True
vect1= =vect3 returns  False
vect2= =vect3 returns  False

vect1!=vect2 returns  False
vect1!=vect3 returns  True
vect2!=vect3 returns  True

Những toán tử nào bạn có thể nạp chồng:

Phạm trù

Toán hạng

Hạn chế

Nhị phân toán học

+, *, /, -, %

Không

Thập phân toán học

+, -, ++, --

Không

Nhị phân bit

&, |, ^, <<, >>

Không

Thập phân bit

!, ~, true, false

Không

So sánh

==, !=, >=, <, <=, >

Phải nạp chồng theo từng cặp.

» Tiếp: Property
« Trước: Lớp Object
Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực
Copied !!!