GT C Sharp cơ bản – Bài 24 : Chương 23: Chồng toàn tử
Chương 23: Chồng toàn tử
Tác giả: Sưu tầm
Khái quát
Chồng toán tử cho phép các toán tử được định nghĩa trên một lớp hay cấu trúc để nó có thể được sử dụng với cú pháp toán tử. Điều này là hữu ích nhất cho các kiểu dữ liệu nơi mà có một định nghĩa tốt cho ý nghĩa của một toán tử nhất định, do đó cho phép đơn giản hoá biểu thức cho người sử dụng.
Việc chồng toán tử quan hệ (==, !=, >, <, >=, <=) được nói rõ trong mục nói về chồng hàm Equals() trong .NET Frameworks, trong Chương 27, “Làm quen với .NET Frameworks”.
Việc chồng các toán tử chuyển đổi kiểu được nói rõ trong Chương 24, “Chuyển đổi kiểu người sử dụng định nghĩa”.
Các toán tử một ngôi
Tất cả các toán tử một ngôi đều được định nghĩa như các hàm tĩnh mà nó lấy một toán tử đơn kiểu lớp hoặc cấu trúc và trả ra một toán tử kiểu đó. Các toán tử sau có thể được chồng:
+ – ! ~ — true false
Sáu toán tử một ngôi đầu tiên được chồng được gọi khi thao tác tương ứng được triệu gọi trên một kiểu. Các toán tử true và false là có sẵn cho các kiểu Boolean nơi mà
if (a == true)
không tương đương với
if (! (a == false))
Điều này xảy ra với các kiểu SQL, mà nó có một tình trạng null là không phải true cũng không phải false. Trong trường hợp này, trình biên dịch sẽ sử dụng các toán tử true và false được chồng để tính toán chính xác như các câu lệnh. Các toán tử này phải trả ra kiểu bool.
Không có cách nào để phân biệt giữa thao tác tăng hay giảm trước và sau. Bởi vì các toán tử là các hàm tĩnh hơn là các hàm thành phần, nên sự phân biệt này là không quan trọng.
Các toán tử nhị phân
Tất cả các toán tử nhị phân cần hai tham số, ít nhất một trong hai phải là kiểu lớp hoặc cấu trúc mà bên trong nó toán tử được khai báo. Một toán tử nhị phân có thể trả ra bất kỳ kiểu nào, nhưng tiêu biểu là trả ra kiểu của lớp hay cấu trúc mà trong đó nó được định nghĩa.
Các toán tử nhị phân sau có thể được định nghĩa:
+ – * / % & | ^ << >> (các toán tử quan hệ)
Một ví dụ
Lớp sau cài đặt vài toán tử có thể chồng:
using System;
struct RomanNumeral {
public RomanNumeral(int value) {
this.value = value;
}
public override string ToString() {
return(value.ToString());
}
public static RomanNumeral operator (RomanNumeral roman) {
return(new RomanNumeral(-roman.value));
}
public static RomanNumeral operator +( RomanNumeral roman1,
RomanNumeral roman2) {
return(new RomanNumeral( roman1.value + roman2.value));
}
public static RomanNumeral operator ++( RomanNumeral roman) {
return(new RomanNumeral(roman.value + 1));
}
int value;
}
class Test {
public static void
RomanNumeral roman1 = new RomanNumeral(12);
RomanNumeral roman2 = new RomanNumeral(125);
Console.WriteLine(“Increment: {0}”, roman1++);
Console.WriteLine(“Addition: {0}”, roman1 + roman2);
}
}
Ví dụ này cho ra kết quả sau:
Increment: 12
Addition: 138
Những hạn chế
Không thể chồng truy xuất thành phần, viện dẫn thành phần (lời gọi hàm), hay các toán tử +, &&, ||, ?:, hoặc new. Đó là vì lợi ích của tính đơn giản; trong khi một toán tử có thể quan tâm các thứ với các phép chồng như vậy, nó tăng thêm khó khăn cho việc hiểu mà, vì lập trình viên sẽ phải luôn luôn nhớ rằng viện dẫn thành phần (ví dụ) có thể đang được làm một cái gì đó đặc biệt. New không thể được chồng bởi vì .NET Runtime chịu trách nhiệm quản lý bộ nhớ, và trong cách diễn đạt của C#, new chỉ có nghĩa là “tạo cho tôi một thể hiện mới”.
Cũng không thể chồng các toán tử gán kết hợp +=, *=,…, vì chúng luôn luôn được mở rộng từ một phép toán đơn lẻ và một phép gán. Điều này tránh đi các trường hợp một toán tử được định nghĩa và một toán tử khác là không, hay (sự rùng mình) chúng sẽ được định nghĩa với các ý nghĩa khác nhau.
Các nguyên tắc thiết kế
Chồng toán tử là một đặc tính chỉ nên được sử dụng khi cần thiết. Từ “cần thiết” có nghĩa là nó làm cho các thứ đơn giản hơn và dễ dàng hơn cho người sử dụng.
Các ví dụ tốt cho chồng toán tử sẽ định nghĩa các phép toán số học trên một số phức hay lớp ma trận.
Các ví dụ sấu sẽ định nghĩa toán tử tăng (++) trong một lớp string để có nghĩa “tăng mỗi ký tự trong chuỗi lên 1”. Một nguyên tắc chỉ đạo tốt đó là trừ phi người sử dụng tiêu biểu hiểu toán tử nào đó thực hiện mà không có bất kỳ tư liệu nào, nó không nên được định nghĩa như một toán tử. Đừng tạo ra nghĩa mới cho các toán tử.
Trong thực tiễn, các toán tử bằng (==) và không bằng (!=) là các toán tử sẽ được định nghĩa hầu như thường xuyên, vì nếu nó không được thực hiện, có thể có các kết quả không mong đợi.
Nếu một kiểu cư xử giống một kiểu dữ liệu dựng sẵn, như lớp BinaryNumeral, thì có ý nghĩa để chồng nhiều toán tử hơn. Tại cái nhìn đầu tiên, nó có vẻ như thế vì lớp BinaryNumeral thật sự là chỉ một số nguyên tưởng tượng, nó chỉ có thể dẫn xuất từ lớp System.Int32, và nhận các toán tử một cách tự do.
Điều này không làm việc vì hai lý do. Thứ nhất, các kiểu giá trị không thể được sử dụng như các lớp cơ sở, và Int32 là một kiểu giá trị. Thứ hai, cho dù là có thể, nó thật sự không làm việc cho BinaryNumeral, bởi vì một BinaryNumeral không phải là một số nguyên; nó chỉ hỗ trợ một phần nhỏ trong phạm vi có thể của số nguyên. Vì vậy, dẫn xuất không thể là lựa chọn thiết kế tốt. Phạm vi nhỏ hơn có nghĩa là cho dù BinaryNumeral được dẫn xuất từ int, thì cũng không có một chuyển đổi kiểu ngầm định từ int sang BinaryNumeral, và do đó bất kỳ biểu thức nào cũng sẽ đòi hỏi các chuyển đổi.
Tuy nhiên, cho dù những cái này không đúng, nó vẫn không có ý nghĩa gì, vì tất cả điểm của kiểu dữ liệu có là để có một số thứ là nhẹ ký, và một cấu trúc sẽ là lựa chọn tốt hơn lớp. Tất nhiên, cấu trúc không thể dẫn xuất từ các đối tượng khác.