Cú pháp khai báo một Constructor là : chúng ta khai báo một phương thức mà cùng tên với lớp và không có kiểu trả về.
public class MyClass
Như trong c++ và java, bạn có thể không cần định nghĩa constructor trong lớp của bạn nếu không cần thiết. Nếu bạn không định nghĩa một constructor nào trong lớp của bạn thì trình biên dịch tạo một constructor mặc định để khởi tạo một số giá trị mặc định như: gán chuỗi rỗng cho chuỗi, gán 0 cho kiểu số, false cho kiểu bool.
Các contructor theo cùng luật overloading như các phương thức khác. Bạn cũng có thể tạo nhiều constructor cùng tên và khác tham số giống như các phương thức nạp chồng :
22 trang |
Chia sẻ: luyenbuizn | Lượt xem: 1148 | Lượt tải: 0
Bạn đang xem trước 20 trang nội dung tài liệu Construction and Disposal, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
Bảng dưới đây trình bày một danh sách đầy đủ của shortcut operators có giá trị trong C#:
Shortcut Operator
Tương đương
x++, ++x
x = x + 1
x--, --x
x = x - 1
x += y
x = x + y
x -= y
x = x – y
x *= y
x = x * y
x /= y
x = x / y
x %= y
x = x % y
x >>= y
x = x >> y
x <<= y
x = x << y
x &= y
x = x & y
x |= y
x = x | y
x ^= y
x = x ^ y
Construction and Disposal
Constructor :
Cú pháp khai báo một Constructor là : chúng ta khai báo một phương thức mà cùng tên với lớp và không có kiểu trả về.
public class MyClass
{
public MyClass()
{
}
// rest of class definition
Như trong c++ và java, bạn có thể không cần định nghĩa constructor trong lớp của bạn nếu không cần thiết. Nếu bạn không định nghĩa một constructor nào trong lớp của bạn thì trình biên dịch tạo một constructor mặc định để khởi tạo một số giá trị mặc định như: gán chuỗi rỗng cho chuỗi, gán 0 cho kiểu số, false cho kiểu bool.
Các contructor theo cùng luật overloading như các phương thức khác. Bạn cũng có thể tạo nhiều constructor cùng tên và khác tham số giống như các phương thức nạp chồng :
public MyClass() // zero-parameter constructor
{
// construction code
}
public MyClass(int number) // another overload
{
// construction code
}
Chú ý : khi bạn đã định nghĩa một constructor trong lớp của bạn thì trình biên dịch sẽ không tự động tạo ra constructor mặc định.
Chúng ta có thể định nghĩa các constructor với các bổ từ private và protected để chúng không thể được nhìn thấy trong các lớp không có quan hệ:
public class MyNumber{ private int number; private MyNumber(int number) // another overload { this.number = number; }}
Chú ý: Nếu bạn định nghĩa một hay nhiều constructor private thì những lớp thừa kế lớp của bạn sẽ không thể khởi tạo được. Do đó chúng ta phải cân nhắc kỹ lưỡng khi định nghĩa bổ từ của một constructor.
Constructor tĩnh(static):
Chúng ta định nghĩa một constructor tĩnh để khởi tạo giá trị cho các biến tĩnh.
class MyClass
{
static MyClass()
{
// initialization code
}
// rest of class definition
}
Một nguyên nhân để viết một constructor tĩnh là: Nếu lớp của bạn có một số trường hay thuộc tính cần được khởi tạo từ bên ngoài trước khi lớp được sử dụng lần đầu tiên.
Chúng ta không thể biết chắc được khi nào một constructor tĩnh sẽ được thực hiện. Và chúng ta cũng không biết trước các constructor tĩnh của các lớp khác nhau sẽ thực hiện những gì. Nhưng chúng ta có thể chắc chắn rằng constructor tĩnh chỉ chạy một lần và nó sẽ được gọi trước khi đoạn mã của bạn tham khảo đến lớp đó. Trong C#, Constructor tĩnh thường được thực hiện ngay trước lần gọi đầu tiên của một thành viên trong lớp đó.
Constructor tĩnh không có bổ từ truy cập, không có bất kỳ một tham số nào và chỉ có duy nhất một constructor tĩnh trong một lớp.
Chúng ta có thể định nghĩa một constructor tĩnh và một constructor thực thể không có tham số trong cùng một lớp. Nó không gây ra bất kỳ một sự xung đột nào bởi vì constructor tĩnh được thực hiện khi lớp được khởi tạo còn constructor thực thể được thực hiện khi một thực thể được tạo ra.
Gọi các constructor từ những constructor khác:
Xét ví dụ như sau:
class Car
{
private string description;
private uint nWheels;
public Car(string model, uint nWheels)
{
this.description = description;
this.nWheels = nWheels;
}
public Car(string model)
{
this.description = description;
this.nWheels = 4;
}
// etc.
Ta thấy cả hai constructor đều khởi tạo cùng các trường, và nó sẽ ngắn gọn hơn nếu ta chỉ cần viết đoạn mã ở một constructor. C# cho phép ta làm đều đó như sau:
class Car
{
private string description;
private uint nWheels;
public Car(string model, uint nWheels)
{
this.description = description;
this.nWheels = nWheels;
}
public Car(string model) : this(model, 4)
{
}
// etc
Khi ta khởi tạo một biến như sau:
Car myCar = new Car("Proton Persona");
Thì constructor 2 tham số sẽ được thực thi trước bất kỳ đoạn mã nào trong constructor 1 biến.
Constructor của các lớp thừa hưởng:
Khi chúng ta tạo ra một thể hiện của một lớp thừa hưởng thì không phải chỉ những constructor của lớp thừa hưởng đó được thực hiện mà cả những constructor của lớp cơ sở cũng được gọi. Và các constructor của lớp cơ sở sẽ được thực hiện trước khi các constructor của lớp thừa hưởng.
Chúng ta xét ví dụ sau:
abstract class GenericCustomer
{
private string name;
// lots of other methods etc.
}
class Nevermore60Customer : GenericCustomer
{
private uint highCostMinutesUsed;
// other methods etc.
}
Đều chúng ta cần ở ví dụ trên là khi một thể hiện của lớp Nevermore60Customer được tạo ra thì thuộc tính name phải được khởi tạo giá trị null và thuộc tính highCostMinutesUsed được khởi tạo là 0.
GenericCustomer arabel = new Nevermore60Customer();
Đối với thuộc tính highCostMinutesUsed thì không có vấn đề gì, nó sẽ được constructor mặc định khởi tạo giá trị 0. Còn thuộc tính name thì sao? Lớp con không thể truy cập vào thuộc tính này bởi vì nó được khai báo private. Nhưng trên thực tế thì thuộc tính này luôn được khởi tạo giá trị null vì khi này constructor của lớp cơ sở cũng được gọi và nó thực hiện trước khởi tạo giá trị null cho thuộc tính name.
Thêm một constructor không tham số trong một quan hệ thừa kế:
Chúng ta sẽ xem xét chuyện gì sẽ xảy ra nếu ta thay thế constructor mặc định bằng một constructor khác không có tham số. Xét ví dụ ở trên, bây giờ ta muốn khởi tạo name bằng giá trị ta làm như sau:
public abstract class GenericCustomer { private string name; public GenericCustomer() : base() // chúng ta có thể xoá bỏ dòng này mà không có ảnh hưởng gì khi biên dịch { name = ""; }Điểm chú ý ở đây là chúng ta thêm lời gọi tường minh đến constructor của lớp cơ sở trước khi constructor của lớp GenericCustomer được thực hiện và chúng ta sử dụng từ khoá base để gọi các constructor ở lớp cơ sở.
Trên thực tế chúng ta có thể viết như sau:
public GenericCustomer()
{
name = "";
}
Nếu trình biên dịch không thấy bất kỳ một sự tham khảo nào đến các constructor khác thì nó sẽ nghĩ là chúng ta muốn gọi constructor mặc định của lớp cơ sở.
Chú ý: Từ khoá base và this chỉ cho phép dùng để gọi một constructor khác, nếu không nó sẽ báo lỗi.
Nếu chúng ta khai báo như sau:
private GenericCustomer()
{
name = "";
}
Thì khi khởi tạo một thể hiện của lớp thừa hưởng neverMore60Customer trình biên dịch sẽ báo lỗi :
'Wrox.ProCSharp.OOCSharp.GenericCustomer.GenericCustomer()' is inaccessible due to its protection level
Bởi vì bạn đã khai báo private nên lớp con sẽ không nhìn thấy constructor này nên sẽ báo lỗi.
Thêm các constructor có tham số trong một quan hệ thừa kế.
Cũng ví dụ như ở trên nhưng bây giờ chúng ta yêu cầu thuộc tính name phải được khởi tạo một giá trị xát định. Tức là ta phải tạo một constructor một tham số ở lớp GenericCustomer:
abstract class GenericCustomer
{
private string name;
public GenericCustomer(string name)
{
this.name = name;
}
Khi đó nếu ta không sửa constructor ở lớp thừa hưởng thì trình biên dịch sẽ báo lỗi vì nó không tìm thấy một constructor không tham số nào trong lớp cơ sở. Vì thế ta phải sửa như sau:
class Nevermore60Customer : GenericCustomer
{
private uint highCostMinutesUsed;
public Nevermore60Customer(string name)
: base(name)
{
}
Xét constructor một tham số ta thấy mặc dù không có quyền truy cập đến thuộc tính name của lớp cơ sở nhưng nó vẫn khởi tạo được thuộc tính name bởi vì constructor của lớp cơ sở đã được gọi thông qua từ khóa base.
Bây giờ ta xét một trường hợp phức tạp hơn:
The Nevermore60Customer definition will look like this at this stage:
class Nevermore60Customer : GenericCustomer
{
public Nevermore60Customer(string name, string referrerName)
: base(name)
{
this.referrerName = referrerName;
}
private string referrerName;
private uint highCostMinutesUsed;
public Nevermore60Customer(string name)
: this(name, "")
{
}
Bây giờ ta khởi tạo một thể hiện như sau:
GenericCustomer arabel = new Nevermore60Customer("Arabel Jones");
Ta thấy trình biên dịch sẽ cần một constructor một tham số để lấy một chuỗi và nó sẽ nhận ra constructor:
public Nevermore60Customer(string name) : this(name, "") { } Khi ta khởi tạo thể hiện arabel thì constructor của nó sẽ được gọi. Ngay lập tức nó chuyến quyền điều khiển cho constructor 2 tham số của lớp Nervemore60customer sẽ gán hai giá trị Arabel Jone và . Sau đó chuyển quyền điều khiển cho constructor 1 tham số của lớp GenericCustomer với chuỗi "Arabel Jone". Và tiếp tục chuyển quyền điều khiển cho constructor system.object thực hiện gán chuỗi "Arable Jone" cho thuộc tính name. Sau đó constructor 2 tham số của lớp Nervemore60customer lấy lại quyền điều khiển và khởi tạo referrerName bằng . Và cuối cùng constructor 1 tham số của lớp Nervemore60customer lấy lại quyền điều khiển và nó không làm gì hết.
Như vậy ta đã hiểu rõ về constructor và cách thức mà chúng hoạt động để biết cách sử dụng đúng trong thực tiễn.
Destructors và phương thức Dispose()
C# cũng hỗ trợ Destructor, nhưng chúng không được dùng thường xuyên như trong C++ và cách chúng hoạt động rất khác nhau. Bởi vì các đối tượng trong .NET và C# thì bị xoá bởi bộ thu gom rác (garbage collection). Trong C#, mẫu destruction làm việc theo hai giai đoạn:
1.Lớp sẽ thực thi giao diện System.IDisposable, tức là thực thi phương thức IDisposable.Dispose(). Phương thức này được gọi tường minh khi trong đoạn mã khi một đối tượng không cần nữa.
2. Một Destructor có thể được định nghĩa và nó được tự động gọi khi đối tượng bị thu gom rác. Destructor chỉ đóng vai trò như một máy rà soát lại trong một số trường hợp xấu client không gọi phương thức Dispose().
Nhìn chung các đối tượng của một vài lớp có thể chứa sự tham khảo đến các đối tượng quản lý khác.Các đối tượng này rất lớn và nên được xoá càng sớm càng tốt, sau đó lớp đó nên thực thi phương thức Dispose(). Nếu một lớp nắm những tài nguyên không quản lý thì nó nên thực thi cả hai phương thức Dispose() và một Destructor.
Cú pháp để định nghĩa Destructor và Dispose():
class MyClass : IDisposable
{
public void Dispose()
{
// implementation
}
~MyClass() // destructor. Only implement if MyClass directly holds
// unmanaged resources.
{
// implementation
}
// etc.
Phương thức Dispose() giống như một phương thức bình thường, nó không có kiểu trả về và không có tham số truyền.
Cú pháp của một Destructor giống như một phương thức nhưng có cùng tên với lớp, có tiền tố là một dấu sóng(~), không có kiểu trả về, không có tham số truyền và không có bổ từ.
Thực thi phương thức Dispose() và một Destructor:
Destrutor được gọi khi một đối tượng bị huỹ. Có một vài điểm ta phải nhớ như sau:
1. Chúng ta không thể biết trước khi nào một thể hiện bị huỹ, tức là ta không biết trước khi nào một Destructor được gọi.
2. Bạn có thể tác động đến bộ thu gom rác để chạy tại một thời điểm trong đoạn mã của bạn bằng cách gọi phương thức System.GC.Collect(). System.GC là một lớp cơ sở .NET mô tả bộ thu gom rác và phương thức Collect() dùng để gọi bộ thu gom rác.
3. Có một lời khuyên là chúng ta không nên thực thi một Destructor nếu như lớp của bạn không thực sự cần đến nó. Nếu một đối tượng thực thi một Destructor, thì nó đặt một đặc tính quan trọng vào quá trình thu gom rác của đối tượng đó. Nó sẽ trì hoãn việc di chuyển cuối cùng của đối tượng từ bộ nhớ. Những đối tượng không có một Destructor thì bị xoá khỏi bộ nhớ trong một lần hoạt động của bộ thu gom rác. Còn đối tượng có Destructor thì nó sẽ qua hai bước : lần đầu nó gọi Destructor mà không xoá đối tượng, lần thứ hai mới thực sự xoá đối tượng.
Chú ý : Không có bất kỳ một tham số nào trong một Destructor, không kiểu trả về và không có bổ từ. Không cần thiết phải gọi tường minh một Destructor của lớp cơ sở mà trình biên dịch sẽ tự động sắp xếp tất cả Destructor được định nghĩa trong các lớp thừa kế đều được gọi.
Close() vs. Dispose()
Sự khác nhau giữa hai phương thức Close() và Dispose() là rất lớn. Phương thức Close() sẽ đóng tài nguyên và có thể được gọi lại sau này. Còn khi gọi Dispose() tức là client đã kết thúc. Bạn có thể thực thi một hoặc cả hai phương thức trên, tuy nhiên để tránh một số rắc rối bạn nên căn nhắc trước khi thực thi chúng. và bạn nên sử dụng Dispose() nếu bạn muốn lấy một số lợi ích từ cấu trúc của giao diện IDisposable.
Sử dụng giao diện IDisposable:
C# đưa ra cú pháp để chắc chắn rằng phương thức Dispose() phải tự động được gọi khi một đối tượng tham khảo ra ngoài phạm vi. Ví dụ ta có một lớp ResourceGobbler sử dụng một số tài nguyên bên ngoài và chúng ta cần khởi tạo một thể hiện của lớp này:
{
ResourceGobbler theInstance = new ResourceGobbler();
// do your processing
theInstance.Dispose();
}
Theo đoạn mã trên thì phương thức Dispose() sẽ được gọi vào cuối khối mã khi đó thể hiện theInstance sẽ bị huỹ. Và chúng ta còn một cách khác như sau:
class ResourceGobbler : IDisposable{ // etc. public void Dispose() { // etc. }}
Ta thấy đoạn mã trên lớp ResourceGobbler thừa kế giao diện IDisposable, việc thừa kế một giao diện khác với việc thừa kế một lớp. Nó sẽ bắt buột lớp ResourceGobbler phải thực thi phương thức Dispose(). Bạn sẽ bị báo lỗi nếu bạn thừa kế từ giao diện IDisposable và không thực thi phương thức Dispose(). Chính vì thế trình biên dịch có thể kiểm tra xem một đối tượng được định nghĩa có phương thức Dispose() thì nó phải tự động được gọi.
Sự thực thi của các Destructor và phương thức Dispose():
Xét ví dụ một lớp chứa cả tài nguyên không quản lý và tài nguyên quản lý:
public class ResourceGobbler : IDisposable
{
private StreamReader sr;
private int connection;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (sr != null)
{
sr.Close();
sr = null;
}
}
CloseConnection();
}
~ResourceGobbler()
{
Dispose (false);
}
void CloseConnection()
{
// code here will close connection
}
}
Lớp ResourceGobbler có một sự tham khảo đến một đối tượng StreamReader. Đây là một lớp được định nghĩa trong System.IO để đọc dữ liệu như tập tin.Tuy nhiên có một sự kết nối gọi bên trong để mô tả một số đối tượng không quản lý. Tức là chúng ta cần thực thi một Destructor. Trong đoạn mã trên thì phương thức CloseConnection() dùng để đóng tài nguyên ngoài.
Các trường chỉ đọc( Readonly fields):
Hằng là một biến mà chứa một giá trị không thay đổi. Trong C# khái niệm hằng cũng giống như mọi ngôn ngữ khác. Tuy nhiên, hằng thì không cần thiết dùng trong mọi trường hợp. Trong một số trường hợp, bạn có một số biến không bị thay đổi nhưng giá trị của nó chỉ được khởi tạo khi chương trình thực thi. C# hỗ trợ một kiểu biến khác có lợi hơn trong trường hợp này là: các trường chỉ đọc.
Từ khoá readonly thì linh động hơn từ khoá const. Bởi vì bạn có thể khởi tạo giá trị cho một trường chỉ đọc trong constructor. Và nó cho phép một trường chỉ đọc là một thể hiện(instance) hay một trường static có các giá trị khác nhau trong mỗi thể hiện của lớp đó. Nếu bạn muốn một trường chỉ đọc là static thì bạn phải khai báo tường minh.
Ví dụ: chúng ta có một chương trình chỉnh sửa tài liệu, và chúng ta muốn hạn chế số tài liệu được sửa đồng thời. Nhưng bởi vì ta bán phần mềm đó và người sử dụng có thể nâng cấp bản của họ. Chúng ta không thể cố định số lượng lớn nhất trong mã nguồn. Chúng ta cần một trường để mô tả giá trị lớn nhất này. Trường này chỉ được đọc tại mỗi lúc chương trình được ném ra:
public class DocumentEditor { public static readonly uint MaxDocuments; static DocumentEditor() { // code here will read in the value of the max no. of documents. // for the sake of argument, let's assume the result is 20 MaxDocuments = 20; }
Trong ví dụ trên thì giá trị lớn nhất chỉ được cần được lưu trữ khi một thể hiện của chương trình thực thi. Và đó là lý do tại sao nó được khởi tạo trong một constructor tĩnh. Tình huống khác, nếu mọi tài liệu chúng ta chỉnh sửa có một ngày được tạo ra và chúng ta không muốn người sử dụng chỉnh sửa nó. Kiểu ngày thì được mô tả trong lớp system.Datetime
public class Document { public readonly DateTime CreationDate; public Document() { // read in creation date from file. Assume result is 1 Jan 2002 // but in general this can be different for different instances // of the class CreationDate = new DateTime(2002, 1, 1); } }
Từ hai ví dụ trên ta thấy CreationDate and MaxDocuments là các trường chỉ đọc và nó không thể được gán giá trị bên ngoài constructor.
Struct
Cú pháp để định nghĩa một struct được mô tả trong ví dụ sau:
struct Dimensions
{
public double Length;
public double Width;
}
Ta thấy Dimensions được định nghĩa như trên gọi là một struct. Một struct dùng để nhóm một số dữ liệu lại với nhau. Trong C#, một struct được định nghĩa gần giống như một lớp chỉ khác từ khoá và một vài điểm như sau:
1.Struct là một kiểu giá trị, không phải là kiểu tham khảo.
2. Struct không hổ trợ thừa kế.
3. Có vài sự khác nhau trong cách làm việc của các constructor đối với struct. Trình biên dịch luôn luôn cung cấp một constructor không tham số mặc định, và không được cho phép thay thế.
4. Với một struct, bạn có thể chỉ rỏ cách mà các trường được đặt ngoài bộ nhớ.
Struct là các kiểu giá trị:
Mặc dù struct là kiểu giá trị nhưng cú pháp để sử dụng nó giống như sử dụng lớp. Ví dụ như bạn khai báo như trên thì bạn có thể viết như sau:
Dimensions point = new Dimensions();
point.Length = 3;
point.Width = 6;
Chú ý rằng struct là các kiểu giá trị, do đó thao tác new sẽ không làm việc theo cách của lớp hay những kiểu tham khảo khác. Nó chỉ đơn giản định vị trên bộ nhớ và gọi constructor thích hợp để khởi tạo các trường. Do đó bạn hoàn toàn có thể viết:
Dimensions point;
point.Length = 3;
point.Width = 6;
Nếu Dimensions là một lớp thì đoạn mã trên sẽ báo lỗi nhưng là một struct thì hoàn toàn hợp lý. Bởi vì là một struct rất dể để gán giá trị. Nhưng trình biên dịch sẽ báo lỗi nếu bạn viết mã như sau:
Dimensions point;
Double D = point.Length;
Trình biên dịch sẽ báo bạn đã sử dụng một biến chưa khởi tạo. Và khi sử dụng struct bạn phải tuân thủ một số quy định sau cho mọi kiểu dữ liệu :
Mọi thứ đều phải được khởi tạo trước khi sử dụng. Một struct được xem như được khởi tạo đầy đủ khi thao tác new được gọi hay khi tất cả các trường đều được gán giá trị. Một struct được định nghĩa là một trường thành viên của một lớp thì nó sẽ được tự động khởi tạo khi đối tượng khởi tạo.
Struct và thừa kế:
Struct không hổ trợ thừa kế, tức là bạn không thể thừa kế từ một struct khác, hay từ bất kỳ lớp nào lớp. Nhưng nó cũng như các kiểu dữ liệu khác đều thừa kế từ lớp System.object. Và chúng ta có thể override trong một struct. Ví dụ ta override phương thức ToString() trong struct sau:
struct Dimensions
{
public double Length;
public double Width;
Dimensions(double length, double width)
{ Length=length; Width=width; }
public override string ToString()
{
return "( " + Length.ToString() + " , " + Width.ToString() + " )";
}
}
Chúng ta khai báo phương thức như làm trong một lớp. Nhưng chú ý rằng không được khai báo virtual, abstract, hay sealed trong bất kỳ thành viên nào của struct. Một lớp thì có thể thừa kế từ một struct nhưng làm ngược lại thì không cho phép.
Constructor cho struct :
Bạn có thể định nghĩa constructor cho các struct như làm với lớp. Nhưng bạn không được phép định nghĩa constructor không có tham số. Có một số trường hợp hiếm thấy trong thời gian chạy của .NET sẽ không thể gọi một constructor không tham số mà bạn cung cấp. Chính vì thế microsoft đã cấm định nghĩa constructor không tham số cho struct trong C#.
Bạn có thể cung cấp một phương thức Close() hay Dispose() cho một struct nhưng bạn không được phép định nghĩa Destructor.
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 đ
Các file đính kèm theo tài liệu này:
- 113.doc