Nạp chồng toán hạng :
Đ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;
// assume a, b and c have been initialized
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ực thi chương trình trên
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
Thực thi chương trình trên
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.
. é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á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