C Sharp căn bản – Chương IV – XÂY DỰNG LỚP – ĐỐI TƯỢNG
Chương 3 thảo luận rất nhiều kiểu dữ liệu cơ bản của ngôn ngữ C#, như int, long and char. Tuy nhiên trái tim và linh hồn của C# là khả năng tạo ra những kiểu dữ liệu mới, phức tạp. Người lập trình tạo ra các kiểu dữ liệu mới bằng cách xây dựng các lớp đối tượng và đó cũng chính là các vấn đề chúng ta cần thảo luận trong chương này.
Đây là khả năng để tạo ra những kiểu dữ liệu mới, một đặc tính quan trọng của ngôn ngữ lập trình hướng đối tượng. Chúng ta có thể xây dựng những kiểu dữ liệu mới trong ngôn ngữ C# bằng cách khai báo và định nghĩa những lớp. Ngoài ra ta cũng có thể định nghĩa các kiểu dữ liệu với những giao diện (interface) sẽ được bàn trong Chương 8 sau. Thể hiện của một lớp được gọi là những đối tượng (object). Những đối tượng này được tạo trong bộ nhớ khi chương trình được thực hiện.
Sự khác nhau giữa một lớp và một đối tượng cũng giống như sự khác nhau giữa khái niệm giữa loài mèo và một con mèo Mun đang nằm bên chân của ta. Chúng ta không thể đụng chạm hay đùa giỡn với khái niệm mèo nhưng có thể thực hiện điều đó được với mèo Mun, nó là một thực thể sống động, chứ không trừu tượng như khái niệm họ loài mèo.
Một họ mèo mô tả những con mèo có các đặc tính: có trọng lượng, có chiều cao, màu mắt, màu lông,…chúng cũng có hành động như là ăn ngủ, leo trèo,…một con mèo, ví dụ như mèo Mun chẳng hạn, nó cũng có trọng lượng xác định là 5 kg, chiều cao 15 cm, màu mắt đen, lông đen…Nó cũng có những khả năng như ăn ngủ leo trèo,..
Lợi ích to lớn của những lớp trong ngôn ngữ lập trình là khả năng đóng gói các thuộc tính và tính chất của một thực thể trong một khối đơn, tự có nghĩa, tự khả năng duy trì . Ví dụ khi chúng ta muốn sắp nội dung những thể hiện hay đối tượng của lớp điều khiển ListBox trên Windows, chỉ cần gọi các đối tượng này thì chúng sẽ tự sắp xếp, còn việc chúng làm ra sao thì ta không quan tâm, và cũng chỉ cần biết bấy nhiêu đó thôi.
Đóng gói cùng với đa hình (polymorphism) và kế thừa (inheritance) là các thuộc tính chính yếu của bất kỳ một ngôn ngữ lập trình hướng đối tượng nào.
Chương 4 này sẽ trình bày các đặc tính của ngôn ngữ lập trình C# để xây dựng các lớp đối tượng. Thành phần của một lớp, các hành vi và các thuộc tính, được xem như là thành viên của lớp (class member). Tiếp theo chương cũng trình này khái niệm về phương thức (method) được dùng để định nghĩa hành vi của một lớp, và trạng thái của các biến thành viên hoạt động trong một lớp. Một đặc tính mới mà ngôn ngữ C# đưa ra để xây dựng lớp là khái niệm thuộc tính (property), thành phần thuộc tính này hoạt động giống như cách phương thức để tạo một lớp, nhưng bản chất của phương thức này là tạo một lớp giao diện cho bên ngoài tương tác với biến thành viên một cách gián tiếp, ta sẽ bàn sâu vấn đề này trong chương.
Định nghĩa lớp
Để định nghĩa một kiểu dữ liệu mới hay một lớp đầu tiên phải khai báo rồi sau đó mới định nghĩa các thuộc tính và phương thức của kiểu dữ liệu đó. Khai báo một lớp bằng cách sử dụng từ khoá class. Cú pháp đầy đủ của khai báo một lớp như sau:
[Thuộc tính] [Bổ sung truy cập] class <Định danh lớp> [: Lớp cơ sở]
{
<Phần thân của lớp: bao gồm định nghĩa các thuộc tính và
phương thức hành động >
}
Thành phần thuộc tính của đối tượng sẽ được trình bày chi tiết trong chương sau, còn thành phần bổ sung truy cập cũng sẽ được trình bày tiếp ngay mục dưới. Định danh lớp chính là tên của lớp do người xây dựng chương trình tạo ra. Lớp cơ sở là lớp mà đối tượng sẽ kế thừa để phát triển ta sẽ bàn sau. Tất cả các thành viên của lớp được định nghĩa bên trong thân của lớp, phần thân này sẽ được bao bọc bởi hai dấu ({}).
Ghi chú: Trong ngôn ngữ C# phần kết thúc của lớp không có đấu chấm phẩy giống như khai báo lớp trong ngôn ngữ C/C++. Tuy nhiên nếu người lập trình thêm vào thì trình biên dịch C# vẫn chấp nhận mà không đưa ra cảnh báo lỗi.
Trong C#, mọi chuyện đều xảy ra trong một lớp. Như các ví dụ mà chúng ta đã tìm hiểu trong chương 3, các hàm điều được đưa vào trong một lớp, kể cả hàm đầu vào của chương trình (hàm
public class Tester
{
public static int
{
//....
}
}
Điều cần nói ở đây là chúng ta chưa tạo bất cứ thể hiện nào của lớp, tức là tạo đối tượng cho
lớp Tester. Điều gì khác nhau giữa một lớp và thể hiện của lớp? để trả lới cho câu hỏi này chúng ta bắt đầu xem xét sự khác nhau giữa kiểu dữ liệu int và một biến kiểu int . Ta có viết như sau:
int var1 = 10;
tuy nhiên ta không thể viết được
int = 10;
Ta không thể gán giá trị cho một kiểu dữ liệu, thay vào đó ta chỉ được gán dữ liệu cho một đối tượng của kiểu dữ lịêu đó, trong trường hợp trên đối tượng là biến var1.
Khi chúng ta tạo một lớp mới, đó chính là việc định nghĩa các thuộc tính và hành vi của tất cả các đối tượng của lớp. Giả sử chúng ta đang lập trình để tạo các điều khiển trong các ứng dụng trên Windows, các điều khiển này giúp cho người dùng tương tác tốt với Windows, như là ListBox, TextBox, ComboBox,…Một trong những điều khiển thông dụng là ListBox, điều khiển này cung cấp một danh sách liệt kê các mục chọn và cho phép người dùng chọn các mục tin trong đó.
ListBox này cũng có các thuộc tính khác nhau nhu: chiều cao, bề dày, vị trí, và màu sắc thể hiện và các hành vi của chúng như: chúng có thể thêm bới mục tin, sắp xếp,…
Ngôn ngữ lập trình hướng đối tượng cho phép chúng ta tạo kiểu dữ liệu mới là lớp ListBox, lớp này bao bọc các thuộc tính cũng như khả năng như: các thuộc tính height, width, location, color, các phương thức hay hành vi như Add(), Remove(), Sort(),…
Chúng ta không thể gán dữ liệu cho kiểu ListBox, thay vào đó đầu tiên ta phải tạo một đối tượng cho lớp đó:
ListBox myListBox;
Một khi chúng ta đã tạo một thể hiện của lớp ListBox thì ta có thể gán dữ liệu cho thể hiện đó. Tuy nhiên đoạn lệnh trên chưa thể tạo đối tượng trong bộ nhớ được, ta sẽ bàn tiếp. Bây giờ ta sẽ tìm hiểu cách tạo một lớp và tạo các thể hiện thông qua ví dụ minh họa 4.1. Ví dụ này tạo một lớp có chức năng hiểu thị thời gian trong một ngày. Lớp này có hành vi thể hiện ngày, tháng, năm, giờ, phút, giây hiện hành. Để làm được điều trên thì lớp này có 6 thuộc tính hay còn gọi là biến thành viên, cùng với một phương thức như sau:
Ví dụ 4.1: Tạo một lớp Thoigian đơn giản như sau.
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
using System;
public class ThoiGian
{
public void ThoiGianHienHanh()
{
Console.WriteLine(“Hien thi thoi gian hien hanh”);
}
// Các biến thành viên int
int Thang; int Ngay; int Gio;
int Phut;
int Giay;
}
public class Tester
{
static void
{
ThoiGian t = new ThoiGian();
t.ThoiGianHienHanh();
}
}
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Kết quả:
Hien thi thoi gian hien hanh
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Lớp ThoiGian chỉ có một phương thức chính là hàm ThoiGianHienHanh(), phần thân của phương thức này được định nghĩa bên trong của lớp ThoiGian. Điều này khác với ngôn ngữ C++, C# không đòi hỏi phải khai báo trước khi định nghĩa một phương thức, và cũng không hỗ trợ việc khai báo phương thức trong một tập tin và sau đó định nghĩa ở một tập tin khác.
C# không có các tập tin tiêu đề, do vậy tất cả các phương thức được định nghĩa hoàn toàn bên trong của lớp. Phần cuối của định nghĩa lớp là phần khai báo các biến thành viên: Nam, Thang, Ngay, Gio, Phut, va Giay.
Sau khi định nghĩa xong lớp ThoiGian, thì tiếp theo là phần định nghĩa lớp Tester, lớp này có chứa một hàm khá thân thiện với chúng ta là hàm Main(). Bên trong hàm Main có một thể hiện của lớp ThoiGian được tạo ra và gán giá trị cho đối tượng t. Bởi vì t là thể hiện của đối tượng ThoiGian, nên hàm Main() có thể sử dụng phương thức của t:
t.ThoiGianHienHanh();
Thuộc tính truy cập
Thuộc tính truy cập quyết định khả năng các phương thức của lớp bao gồm việc các phương thức của lớp khác có thể nhìn thấy và sử dụng các biến thành viên hay những phương thức bên trong lớp. Bảng 4.1 tóm tắt các thuộc tính truy cập của một lớp trong C#.
Thuộc tính |
Giới hạn truy cập |
public |
Không hạn chế. Những thành viên được đánh dấu public có thể được dùng bởi bất kì các phương thức của lớp bao gồm những lớp khác. |
private |
Thành viên trong một lớp A được đánh dấu là private thì chỉ được truy cập bởi các phương thức của lớp A. |
protected |
Thành viên trong lớp A được đánh dấu là protected thì chỉ được các phương thức bên trong lớp A và những phương thức dẫn xuất từ lớp A truy cập. |
internal |
Thành viên trong lớp A được đánh dấu là internal thì được truy cập bởi những phương thức của bất cứ lớp nào trong cùng khối hợp ngữ với A. |
protected internal |
Thành viên trong lớp A được đánh dấu là protected internal được truy cập bởi các phương thức của lớp A, các phương thức của lớp dẫn xuất của A, và bất cứ lớp nào trong cùng khối hợp ngữ của A. |
Bảng 4.1: Thuộc tính truy cập.
Mong muốn chung là thiết kế các biến thành viên của lớp ở thuộc tính private. Khi đó chỉ có phương thức thành viên của lớp truy cập được giá trị của biến. C# xem thuộc tính private là mặc định nên trong ví dụ 4.1 ta không khai báo thuộc tính truy cập cho 6 biến nên mặc định chúng là private:
// Các biến thành viên private int Nam;
int Thang; int Ngay; int Gio;
int Phut;
int Giay;
Do lớp Tester và phương thức thành viên ThoiGianHienHanh của lớp ThoiGian được khai báo là public nên bất kỳ lớp nào cũng có thể truy cập được.
Ghi chú: Thói quen lập trình tốt là khai báo tường minh các thuộc tính truy cập của biến thành viên hay các phương thức trong một lớp. Mặc dù chúng ta biết chắc chắn rằng các thành viên của lớp là được khai báo private mặc định. Việc khai báo tường minh này sẽ làm cho chương trình dễ hiểu, rõ ràng và tự nhiên hơn.
Tham số của phương thức
Trong các ngôn ngữ lập trình thì tham số và đối mục được xem là như nhau, cũng tương
tự khi đang nói về ngôn ngữ hướng đối tượng thì ta gọi một hàm là một phương thức hay hành vi. Tất cả các tên này điều tương đồng với nhau.
Một phương thức có thể lấy bất kỳ số lượng tham số nào, Các tham số này theo sau bởi
tên của phương thức và được bao bọc bên trong dấu ngoặc tròn (). Mỗi tham số phải khai báo kèm với kiểu dữ liệu. ví dụ ta có một khai báo định nghĩa một phương thức có tên là Method, phương thức không trả về giá trị nào cả (khai báo giá trị trả về là void), và có hai tham số là một kiểu int và button:
void Method( int param1, button param2)
{
//…
}
Bên trong thân của phương thức, các tham số này được xem như những biến cục bộ, giống như là ta khai báo biến bên trong phương thức và khởi tạo giá trị bằng giá trị của tham số truyền vào. Ví dụ 4.2 minh họa việc truyền tham số vào một phương thức, trong trường hợp này thì hai tham số của kiểu là int và float.
Ví dụ 4.2: Truyền tham số cho phương thức.
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
using System;
public class Class1
{
public void SomeMethod(int p1, float p2)
{
Console.WriteLine(“Ham nhan duoc hai tham so: {0} va {1}”, p1,p2);
}
}
public class Tester
{
static void Main()
{
int var1 = 5;
float var2 = 10.5f;
Class1 c = new Class1();
c.SomeMethod( var1, var2 );
}
}
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Kết quả:
Ham nhan duoc hai tham so: 5 va 10.5
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Phương thức SomeMethod sẽ lấy hai tham số int và float rồi hiển thị chúng ta màn hình bằng việc dùng hàm Console.WriteLine(). Những tham số này có tên là p1 và p2 được xem như là biến cục bộ bên trong của phương thức.
Trong phương thức gọi Main, có hai biến cục bộ được tạo ra là var1 và var2. Khi hai biến này được truyền cho phương thức SomeMethod thì chúng được ánh xạ thành hai tham số p1 và
p2 theo thứ tự danh sách biến đưa vào.
Tạo đối tượng
Trong Chương 3 có đề cập đến sự khác nhau giữa kiểu dữ liệu giá trị và kiểu dữ liệu tham chiếu. Những kiểu dữ liệu chuẩn của C# như int, char, float,… là những kiểu dữ liệu giá trị,
và các biến được tạo ra từ các kiểu dữ liệu này được lưu trên stack. Tuy nhiên, với các đối tượng kiểu dữ liệu tham chiếu thì được tạo ra trên heap, sử dụng từ khóa new để tạo một đối tượng:
ThoiGian t = new ThoiGian();
t thật sự không chứa giá trị của đối tượng ThoiGian, nó chỉ chứa địa chỉ của đối tượng được tạo ra trên heap, do vậy t chỉ chứa tham chiếu đến một đối tượng mà thôi.
Bộ khởi dựng
Thử xem lại ví dụ minh họa 4.1, câu lệnh tạo một đối tượng cho lớp ThoiGian tương tự như việc gọi thực hiện một phương thức:
ThoiGian t = new ThoiGian();
Đúng như vậy, một phương thức sẽ được gọi thực hiện khi chúng ta tạo một đối tượng. Phương thức này được gọi là bộ khởi dựng (constructor). Các phương thức này được định nghĩa khi xây dựng lớp, nếu ta không tạo ra thì CLR sẽ thay mặt chúng ta mà tạo phương thức khởi dựng một cách mặc định. Chức năng của bộ khởi dựng là tạo ra đối tượng được xác định bởi một lớp và đặt trạng thái này hợp lệ. Trước khi bộ khởi dựng được thực hiện thì đối tượng chưa được cấp phát trong bộ nhớ. Sau khi bộ khởi dựng thực hiện hoàn thành thì bộ nhớ sẽ
lưu giữ một thể hiện hợp lệ của lớp vừa khai báo.
Lớp ThoiGian trong ví dụ 4.1 không định nghĩa bộ khởi dựng. Do không định nghĩa nên trình biên dịch sẽ cung cấp một bộ khởi dựng cho chúng ta. Phương thức khởi dựng mặc định được tạo ra cho một đối tượng sẽ không thực hiện bất cứ hành động nào, tức là bên trong thân của phương thức rỗng. Các biến thành viên được khởi tạo các giá trị tầm thường như thuộc tính nguyên có giá trị là 0 và chuỗi thì khởi tạo rỗng,..Bảng 4.2 sau tóm tắt các giá trị mặc định
được gán cho các kiểu dữ liệu cơ bản.
Kiểu dữ liệu |
Giá trị mặc định |
int, long, byte,… |
0 |
bool |
false |
char |
‘\0’ (null) |
enum |
0 |
reference |
null |
Bảng 4.2: Giá trị mặc định của kiểu dữ liệu cơ bản.
Thường thường, khi muốn định nghĩa một phương thức khởi dựng riêng ta phải cung cấp các tham số để hàm khởi dựng có thể khởi tạo các giá trị khác ngoài giá trị mặc định cho các đối tượng. Quay lại ví dụ 4.1 giả sử ta muốn truyền thời gian hiện hành: năm, tháng, ngày,…để đối tượng có ý nghĩa hơn.
Để định nghĩa một bộ khởi dựng riêng ta phải khai báo một phương thức có tên giống như tên
lớp đã khai báo. Phương thức khởi dựng không có giá trị trả về và được khai báo là public. Nếu phương thức khởi dựng này được truyền tham số thì phải khai báo danh sách tham số giống như khai báo với bất kỳ phương thức nào trong một lớp. Ví dụ 4.3 được viết lại từ ví dụ 4.1 và thêm một bộ khởi dựng riêng, phương phức khởi dựng này sẽ nhận một tham số là một đối tượng kiểu DateTime do C# cung cấp.
Ví dụ 4.3: Định nghĩa một bộ khởi dựng.
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
using System;
public class ThoiGian
{
public void ThoiGianHienHanh()
{
Console.WriteLine(“ Thoi gian hien hanh la : {0}/{1}/{2}
{3}:{4}:{5}”, Ngay, Thang, Nam, Gio, Phut, Giay);
}
// Hàm khởi dựng
public ThoiGian( System.DateTime dt )
{
Nam = dt.Year; Thang = dt.Month; Ngay = dt.Day;
Gio = dt.Hour; Phut = dt.Minute; Giay = dt.Second;
}
// Biến thành viên private int Nam;
int Thang; int Ngay; int Gio;
int Phut;
int Giay;
}
public class Tester
{
static void Main()
{
System.DateTime currentTime = System.DateTime.Now; ThoiGian t = new ThoiGian( currentTime ); t.ThoiGianHienHanh();
}
}
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Kết quả:
Thoi gian hien hanh la: 5/6/2002 9:10:20
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Trong ví dụ trên phương thức khởi dựng lấy một đối tượng DateTime và khởi tạo tất cả các biến thành viên dựa trên giá trị của đối tượng này. Khi phương thức này thực hiện xong, một đối tượng ThoiGian được tạo ra và các biến của đối tượng cũng đã được khởi tạo. Hàm ThoiGianHienHanh được gọi trong hàm Main() sẽ hiển thị giá trị thời gian lúc đối tượng được tạo ra.
Chúng ta thử bỏ một số lệnh khởi tạo trong phương thức khởi dựng và cho thực hiện chương trình lại thì các biến không được khởi tạo sẽ có giá trị mặc định là 0, do là biến nguyên. Một biến thành viên kiểu nguyên sẽ được thiết lập giá trị là 0 nếu chúng ta không gán nó trong phương thức khởi dựng. Chú ý rằng kiểu dữ liệu giá trị không thể không được khởi tạo, nếu ta không khởi tạo thì trình biên dịch sẽ cung cấp các giá trị mặc định theo bảng 4.2.
Ngoài ra trong chương trình 4.3 trên có sử dụng đối tượng của lớp DateTime, lớp DateTime này được cung cấp bởi thư viện System, lớp này cũng cung cấp các biến thành viên public như: Year, Month, Day, Hour, Minute, và Second tương tự như lớp ThoiGian của chúng ta. Thêm vào đó là lớp này có đưa ra một phương thức thành viên tĩnh tên là Now, phương thức Now sẽ trả về một tham chiếu đến một thể hiện của một đối tượng DateTime được khởi tạo
với thời gian hiện hành. Theo như trên khi lệnh :
System.DataTime currentTime = System.DateTime.Now();
được thực hiện thì phương thức tĩnh Now() sẽ tạo ra một đối tượng DateTime trên bộ nhớ heap và trả về một tham chiếu và tham chiếu này được gán cho biến đối tượng currentTime.
Sau khi đối tượng currentTime được tạo thì câu lệnh tiếp theo sẽ thực hiện việc truyền đối tượng currentTime cho phương thức khởi dựng để tạo một đối tượng ThoiGian:
ThoiGian t = new ThoiGian( currentTime );
Bên trong phương thức khởi dựng này tham số dt sẽ tham chiếu đến đối tượng DateTime là đối tượng vừa tạo mà currentTime cũng tham chiếu. Nói cách khác lúc này tham số dt và currentTime cùng tham chiếu đến một đối tượng DateTime trong bộ nhớ. Nhờ vậy phương thức khởi dựng ThoiGian có thể truy cập được các biến thành viên public của đối tượng DateTime được tạo trong hàm Main().
Có một sự nhấn mạnh ở đây là đối tượng DateTime được truyền cho bộ dựng ThoiGian chính là đối tượng đã được tạo trong hàm Main và là kiểu dữ liệu tham chiếu. Do vậy khi thực hiện truyền tham số là một kiểu dữ liệu tham chiếu thì con trỏ được ánh xạ qua chứ hoàn toàn không có một đối tượng nào được sao chép lại.
Khởi tạo biến thành viên
Các biến thành viên có thể được khởi tạo trực tiếp khi khai báo trong quá trình khởi tạo, thay vì phải thực hiện việc khởi tạo các biến trong bộ khởi dựng. Để thực hiện việc khởi tạo này rất đơn giản là việc sử dụng phép gán giá trị cho một biến:
private int Giay = 30; // Khởi tạo
Việc khởi tạo biến thành viên sẽ rất có ý nghĩa, vì khi xác định giá trị khởi tạo như vậy thì biến sẽ không nhận giá trị mặc định mà trình biên dịch cung cấp. Khi đó nếu các biến này không được gán lại trong các phương thức khởi dựng thì nó sẽ có giá trị mà ta đã khởi tạo. Ví dụ 4.4 minh họa việc khởi tạo biến thành viên khi khai báo. Trong ví dụ này sẽ có hai bộ dựng ngoài bộ dựng mặc định mà trình biên dịch cung cấp, một bộ dựng thực hiện việc gán
giá trị cho tất cả các biến thành viên, còn bộ dựng thứ hai thì cũng tương tự nhưng sẽ không gán giá trị cho biến Giay.
Ví dụ 4.4: Minh hoạ sử dụng khởi tạo biến thành viên.
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
public class ThoiGian
{
public void ThoiGianHienHanh()
{
System.DateTime now = System.DateTime.Now; System.Console.WriteLine(“\n Hien tai: \t {0}/{1}/{2} {3}:{4}:{5}”,
now.Day, now.Month, now.Year, now.Hour, now.Minute, now.Second); System.Console.WriteLine(“ Thoi Gian:\t {0}/{1}/{2} {3}:{4}:{5}”,
Ngay, Thang, Nam, Gio, Phut, Giay);
}
public ThoiGian( System.DateTime dt)
{
Nam = dt.Year; Thang = dt.Month; Ngay = dt.Day;
Gio = dt.Hour; Phut = dt.Minute;
Giay = dt.Second; // có gán cho biến thành viên Giay
}
public ThoiGian(int Year, int Month, int Date, int Hour, int Minute)
{
Nam = Year; Thang = Month; Ngay = Date;
Gio = Hour;
Phut = Minute;
}
private int Nam; private int Thang; private int Ngay; private int Gio; private int Phut;
private int Giay = 30 ; // biến được khởi tạo.
}
public class Tester
{
static void Main()
{
System.DateTime currentTime = System.DateTime.Now; ThoiGian t1 = new ThoiGian( currentTime ); t1.ThoiGianHienHanh();
ThoiGian t2 = new ThoiGian(2001,7,3,10,5);
t2.ThoiGianHienHanh();
}
}
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Kết quả:
Hien tai: |
5/6/2002 |
10:15:5 |
Thoi Gian: |
5/6/2002 |
10:15:5 |
Hien tai: |
5/6/2002 |
10:15:5 |
Thoi Gian: |
3/7/2001 |
10:5:30 |
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Nếu không khởi tạo giá trị của biến thành viên thì bộ khởi dựng mặc định sẽ khởi tạo giá trị là
0 mặc định cho biến thành viên có kiểu nguyên. Tuy nhiên, trong trường hợp này biến thành viên Giay được khởi tạo giá trị 30:
Giay = 30; // Khởi tạo
Trong trường hợp bộ khởi tạo thứ hai không truyền giá trị cho biến Giay nên biến này vẫn lấy giá trị mà ta đã khởi tạo ban đầu là 30:
ThoiGian t2 = new ThoiGian(2001, 7, 3, 10, 5);
t2.ThoiGianHienHanh();
Ngược lại, nếu một giá trị được gán cho biến Giay như trong bộ khởi tạo thứ nhất thì giá trị mới này sẽ được chồng lên giá trị khởi tạo.
Trong ví dụ trên lần đầu tiên tạo đối tượng ThoiGian do ta truyền vào đối tượng DateTime nên hàm khởi dựng thứ nhất được thực hiện, hàm này sẽ gán giá trị 5 cho biến Giay. Còn khi tạo đối tượng ThoiGian thứ hai, hàm khởi dựng thứ hai được thực hiện, hàm này không gán giá trị cho biến Giay nên biến này vẫn còn lưu giữ lại giá trị 30 khi khởi tạo ban đầu.
Bộ khởi dựng sao chép
Bộ khởi dựng sao chép thực hiện việc tạo một đối tượng mới bằng cách sao chép tất cả các biến từ một đối tượng đã có và cùng một kiểu dữ liệu. Ví dụ chúng ta muốn đưa một đối tượng ThoiGian vào bộ khởi dựng lớp ThoiGian để tạo một đối tượng ThoiGian mới có cùng giá trị với đối tượng ThoiGian cũ. Hai đối tượng này hoàn toàn khác nhau và chỉ giống nhau ở giá trị biến thành viên sao khi khởi dựng.
Ngôn ngữ C# không cung cấp bộ khởi dựng sao chép, do đó chúng ta phải tự tạo ra. Việc sao chép các thành phần từ một đối tượng ban đầu cho một đối tượng mới như sau:
public ThoiGian( ThoiGian tg)
{
Nam = tg.Nam; Thang = tg.Thang; Ngay = tg.Ngay; Gio = tg.Gio;
Phut = tg.Phut; Giay = tg.Giay;
}
Khi đó ta có thể sao chép từ một đối tượng ThoiGian đã hiện hữu như sau:
ThoiGian t2 = new ThoiGian( t1 );
Trong đó t1 là đối tượng ThoiGian đã tồn tại, sau khi lệnh trên thực hiện xong thì đối tượng
t2 được tạo ra như bản sao của đối tượng t1.
Từ khóa this
Từ khóa this được dùng để tham chiếu đến thể hiện hiện hành của một đối tượng. Tham chiếu this này được xem là con trỏ ẩn đến tất các phương thức không có thuộc tính tĩnh trong một lớp. Mỗi phương thức có thể tham chiếu đến những phương thức khác và các biến thành viên thông qua tham chiếu this này.
Tham chiếu this này được sử dụng thường xuyên theo ba cách:
Sử dụng khi các biến thành viên bị che lấp bởi tham số đưa vào, như trường hợp sau:
public void SetYear( int Nam)
{
this.Nam = Nam;
}
Như trong đoạn mã trên phương thức SetYear sẽ thiết lập giá trị của biến thành viên Nam, tuy nhiên do tham số đưa vào có tên là Nam, trùng với biến thành viên, nên ta phải dùng tham chiếu this để xác định rõ các biến thành viên và tham số được truyền vào. Khi đó this.Nam chỉ đến biến thành viên của đối tượng, trong khi Nam chỉ đến tham số.
Sử dụng tham chiếu this để truyền đối tượng hiện hành vào một tham số của một phương thức của đối tượng khác:
public void Method1( OtherClass otherObject )
{
// Sử dụng tham chiếu this để truyền tham số là bản
// thân đối tượng đang thực hiện. otherObject.SetObject( this );
}
Như trên cho thấy khi cần truyền một tham số là chính bản thân của đối tượng đang thực hiện
thì ta bắt buộc phải dùng tham chiếu this để truyền.
Các thứ ba sử dụng tham chiếu this là mảng chỉ mục (indexer), phần này sẽ được trình bày chi tiết trong chương 9.
Sử dụng các thành viên tĩnh (static member)
Những thuộc tính và phương thức trong một lớp có thể là những thành viên thể hiện (instance members) hay những thành viên tĩnh (static members). Những thành viên thể hiện hay thành viên của đối tượng liên quan đến thể hiện của một kiểu dữ liệu. Trong khi thành viên tĩnh được xem như một phần của lớp. Chúng ta có thể truy cập đến thành viên tĩnh của một lớp thông qua tên lớp đã được khai báo. Ví dụ chúng ta có một lớp tên là Button và có hai thể hiện của lớp tên là btnUpdate và btnDelete. Và giả sử lớp Button này có một phương thức tĩnh là Show(). Để truy cập phương thức tĩnh này ta viết :
Button.Show();
Đúng hơn là viết:
btnUpdate.Show();
Ghi chú: Trong ngôn ngữ C# không cho phép truy cập đến các phương thức tĩnh và các biến thành viên tĩnh thông qua một thể hiện, nếu chúng ta cố làm điều đó thì trình biên dịch C# sẽ báo lỗi, điều này khác với ngôn ngữ C++.
Trong một số ngôn ngữ thì có sự phân chia giữa phương thức của lớp và các phương thức khác (toàn cục) tồn tại bên ngoài không phụ thuộc bất cứ một lớp nào. Tuy nhiên, điều này không cho phép trong C#, ngôn ngữ C# không cho phép tạo các phương thức bên ngoài của lớp, nhưng ta có thể tạo được các phương thức giống như vậy bằng cách tạo các phương thức tĩnh bên trong một lớp.
Phương thức tĩnh hoạt động ít nhiều giống như phương thức toàn cục, ta truy cập phương thức này mà không cần phải tạo bất cứ thể hiện hay đối tượng của lớp chứa phương thức toàn cục. Tuy nhiên, lợi ích của phương thức tĩnh vượt xa phương thức toàn cục vì phương thức tĩnh được bao bọc trong phạm vi của một lớp nơi nó được định nghĩa, do vậy ta sẽ không gặp tình trạng lộn xộn giữa các phương thức trùng tên do chúng được đặt trong namespace.
Ghi chú: Chúng ta không nên bị cám dỗ bởi việc tạo ra một lớp chứa toàn bộ các phương thức linh tinh. Điều này có thể tiện cho công việc lập trình nhưng sẽ điều không mong muốn
và giảm tính ý nghĩa của việc thiết kế hướng đối tượng. Vì đặc tính của việc tạo các đối tượng là xây dựng các phương thức và hành vi xung quanh các thuộc tính hay dữ liệu của đối tượng.
Gọi một phương thức tĩnh
Như chúng ta đã biết phương thức Main() là một phương thức tĩnh. Phương tĩnh được xem như là phần hoạt động của lớp hơn là của thể hiện một lớp. Chúng cũng không cần có một tham chiếu this hay bất cứ thể hiện nào tham chiếu tới.
Phương thức tĩnh không thể truy cập trực tiếp đến các thành viên không có tính chất tĩnh (nonstatic). Như vậy Main() không thể gọi một phương thức không tĩnh bên trong lớp. Ta xem lại đoạn chương trình minh họa trong ví dụ 4.2:
using System;
public class Class1
{
public void SomeMethod(int p1, float p2)
{
Console.WriteLine(“Ham nhan duoc hai tham so: {0} va {1}”, p1,p2);
}
}
public class Tester
{
static void Main()
{
int var1 = 5;
float var2 = 10.5f;
Class1 c = new Class1();
c.SomeMethod( var1, var2 );
}
}
Phương thức SomeMethod() là phương thức không tĩnh của lớp Class1, do đó để truy cập được phương thức của lớp này cấn phải tạo một thể hiện là một đối tượng cho lớp Class1.
Sau khi tạo thì có thể thông qua đối tượng c ta có thể gọi được được phương thức Some- Method().
Sử dụng bộ khởi dựng tĩnh
Nếu một lớp khai báo một bộ khởi tạo tĩnh (static constructor), thì được đảm bảo rằng phương thức khởi dựng tĩnh này sẽ được thực hiện trước bất cứ thể hiện nào của lớp được tạo ra.
Ghi chú: Chúng ta không thể điều khiển chính xác khi nào thì phương thức khởi dựng tĩnh này được thực hiện. Tuy nhiên ta biết chắc rằng nó sẽ được thực hiện sau khi chương trình chạy và trước bất kì biến đối tượng nào được tạo ra.
Theo ví dụ 4.4 ta có thể thêm một bộ khởi dựng tĩnh cho lớp ThoiGian như sau:
static ThoiGian()
{
Ten = “Thoi gian”;
}
Lưu ý rằng ở đây không có bất cứ thuộc tính truy cập nào như public trước bộ khởi dựng tĩnh. Thuộc tính truy cập không cho phép theo sau một phương thức khởi dựng tĩnh. Do phương thức tĩnh nên không thể truy cập bất cứ biến thành viên không thuộc loại tĩnh, vì vậy biến thành viên Name bên trên cũng phải được khai báo là tĩnh:
private static string Ten;
Cuối cùng ta thêm một dòng vào phương thức ThoiGianHienHanh() của lớp ThoiGian:
public void ThoiGianHienHanh()
{
System.Console.WriteLine(“ Ten: {0}”, Ten); System.Console.WriteLine(“ Thoi Gian:\t {0}/{1}/{2} {3}:{4}:{5}”,
Ngay, Thang, Nam, Gio, Phut, Giay);
}
Sau khi thay đổi ta biên dịch và chạy chương trình được kết quả sau:
Ten: Thoi Gian
Thoi Gian: 5/6/2002 18:35:20
Mặc dù chương trình thực hiện tốt, nhưng không cần thiết phải tạo ra bộ khởi dựng tĩnh để phục vụ cho mục đích này. Thay vào đó ta có thể dùng chức năng khởi tạo biến thành viên như sau:
private static string Ten = “Thoi Gian”;
Tuy nhiên, bộ khởi tạo tĩnh có hữu dụng khi chúng ta cần cài đặt một số công việc mà không
thể thực hiện được thông qua chức năng khởi dựng và công việc cài đặt này chỉ được thực hiện duy nhất một lần.
Sử dụng bộ khởi dựng private
Như đã nói ngôn ngữ C# không có phương thức toàn cục và hằng số toàn cục. Do vậy chúng ta có thể tạo ra những lớp tiện ích nhỏ chỉ để chứa các phương thức tĩnh. Cách thực hiện này luôn có hai mặt tốt và không tốt. Nếu chúng ta tạo một lớp tiện ích như vậy và không muốn bất cứ một thể hiện nào được tạo ra. Để ngăn ngừa việc tạo bất cứ thể hiện của lớp ta tạo ra bộ khởi dựng không có tham số và không làm gì cả, tức là bên trong thân của phương thức rỗng, và thêm vào đó phương thức này được đánh dầu là private. Do không có bộ khởi dựng public, nên không thể tạo ra bất cứ thể hiện nào của lớp.
Sử dụng các thuộc tính tĩnh
Một vấn đề đặt ra là làm sao kiểm soát được số thể hiện của một lớp được tạo ra khi thực hiện chương trình. Vì hoàn toàn ta không thể tạo được biến toàn cục để làm công việc đếm số thể hiện của một lớp.
Thông thường các biến thành viên tĩnh được dùng để đếm số thể hiện đã được được tạo ra của một lớp. Cách sử dụng này được áp dụng trong minh họa sau:
Ví dụ 4.5: Sử dụng thuộc tính tĩnh để đếm số thể hiện.
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
using System;
public class Cat
{
public Cat()
{
instance++;
}
public static void HowManyCats()
{
Console.WriteLine(“{0} cats”, instance);
}
private static int instance =0;
}
public class Tester
{
static void Main()
{
Cat.HowManyCats(); Cat mun = new Cat(); Cat.HowManyCats();
Cat muop = new Cat();
Cat miu = new Cat();
Cat.HowManyCats();
}
}
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Kết quả:
0 cats
1 cats
3 cats
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Bên trong lớp Cat ta khai báo một biến thành viên tĩnh tên là instance biến này dùng để đếm
số thể hiện của lớp Cat, biến này được khởi tạo giá trị 0. Lưu ý rằng biến thành viên tĩnh được xem là thành phần của lớp, không phải là thành viên của thể hiện, do vậy nó sẽ không được khởi tạo bởi trình biên dịch khi tạo các thể hiện. Khởi tạo tường minh là yêu cầu bắt buộc với các biến thành viên tĩnh. Khi một thể hiện được tạo ra thì bộ dựng của lớp Cat sẽ thực hiện tăng biến instance lên một đơn vị.
Hủy đối tượng
Ngôn ngữ C# cung cấp cơ chế thu dọn (garbage collection) và do vậy không cần phải khai báo tường minh các phương thức hủy. Tuy nhiên, khi làm việc với các đoạn mã không được quản lý thì cần phải khai báo tường minh các phương thức hủy để giải phóng các tài nguyên.
C# cung cấp ngần định một phương thức để thực hiện điều khiển công việc này, phương thức
đó là Finalize() hay còn gọi là bộ kết thúc. Phương thức Finalize này sẽ được gọi bởi cơ chế thu dọn khi đối tượng bị hủy.
Phương thức kết thúc chỉ giải phóng các tài nguyên mà đối tượng nắm giữ, và không tham chiếu đến các đối tượng khác. Nếu với những đoạn mã bình thường tức là chứa các tham chiếu kiểm soát được thì không cần thiết phải tạo và thực thi phương thức Finalize(). Chúng ta chỉ làm điều này khi xử lý các tài nguyên không kiểm soát được.
Chúng ta không bao giờ gọi một phương thức Finalize() của một đối tượng một cách trực tiếp, ngoại trừ gọi phương thức này của lớp cơ sở khi ở bên trong phương thức Finalize() của chúng ta. Trình thu dọn sẽ thực hiện việc gọi Finalize() cho chúng ta.
Cách Finalize thực hiện
Bộ thu dọn duy trì một danh sách những đối tượng có phương thức Finalize. Danh sách này được cập nhật mỗi lần khi đối tượng cuối cùng được tạo ra hay bị hủy.
Khi một đối tượng trong danh sách kết thúc của bộ thu dọn được chọn đầu tiên. Nó sẽ được đặt vào hàng đợi (queue) cùng với những đối tượng khác đang chờ kết thúc. Sau khi phương thức Finalize của đối tượng thực thi bộ thu dọn sẽ gom lại đối tượng và cập nhật lại danh sách hàng đợi, cũng như là danh sách kết thúc đối tượng.
Bộ hủy của C#
Cú pháp phương thức hủy trong ngôn ngữ C# cũng giống như trong ngôn ngữ C++. Nhưng về hành động cụ thể chúng có nhiều điểm khác nhau. Ta khao báo một phương thức hủy trong C# như sau:
~Class1() {}
Tuy nhiên, trong ngôn ngữ C# thì cú pháp khai báo trên là một shortcut liên kết đến một phương thức kết thúc Finalize được kết với lớp cơ sở, do vậy khi viết
~Class1()
{
// Thực hiện một số công việc
}
Cũng tương tự như viết :
Class1.Finalize()
{
// Thực hiện một số công việc base.Finalize();
}
Do sự tương tự như trên nên khả năng dẫn đến sự lộn xộn nhầm lẫn là không tránh khỏi, nên chúng ta phải tránh viết các phương thức hủy và viết các phương thức Finalize tường minh nếu có thể được.
Phương thức Dispose
Như chúng ta đã biết thì việc gọi một phương thức kết thúc Finalize trong C# là không hợp lệ, vì phương thức này dành cho bộ thu dọn thực hiện. Nếu chúng ta xử lý các tài nguyên không kiểm soát như xử lý các handle của tập tin và ta muốn được đóng hay giải phóng nhanh chóng bất cứ lúc nào, ta có thực thi giao diện IDisposable, phần chi tiết IDisposable sẽ được trình bày chi tiết trong Chương 8. Giao diện IDisposable yêu cầu những thành phần thực thi của nó định nghĩa một phương thức tên là Dispose() để thực hiện công việc dọn dẹp mà ta yêu cầu. Ý nghĩa của phương thức Dispose là cho phép chương trình thực hiện các công việc dọn dẹp hay giải phóng tài nguyên mong muốn mà không phải chờ cho đến khi phương thức Finalize() được gọi.
Khi chúng ta cung cấp một phương thức Dispose thì phải ngưng bộ thu dọn gọi phương thức Finalize() trong đối tượng của chúng ta. Để ngưng bộ thu dọn, chúng ta gọi một phương thức tĩnh của lớp GC (garbage collector) là GC.SuppressFinalize() và truyền tham số là tham chiếu this của đối tượng. Và sau đó phương thức Finalize() sử dụng để gọi phương thức Dispose() như đoạn mã sau:
public void Dispose()
{
// Thực hiện công việc dọn dẹp
// Yêu cầu bộ thu dọc GC trong thực hiện kết thúc
GC.SuppressFinalize( this );
}
public override void Finalize()
{
Dispose();
base.Finalize();
}
Phương thức Close
Khi xây dựng các đối tượng, chúng ta có muốn cung cấp cho người sử dụng phương thức
Close(), vì phương thức Close có vẻ tự nhiên hơn phương thức Dispose trong các đối tượng
có liên quan đến xử lý tập tin. Ta có thể xây dựng phương thức Dispose() với thuộc tính là
private và phương thức Close() với thuộc tính public. Trong phương thức Close() đơn giản
là gọi thực hiện phương thức Dispose().
Câu lệnh using
Khi xây dựng các đối tượng chúng ta không thể chắc chắn được rằng người sử dụng có
thể gọi hàm Dispose(). Và cũng không kiểm soát được lúc nào thì bộ thu dọn GC thực hiện việc dọn dẹp. Do đó để cung cấp khả năng mạnh hơn để kiểm soát việc giải phóng tài nguyên thì C# đưa ra cú pháp chỉ dẫn using, cú pháp này đảm bảo phương thức Dispose() sẽ được gọi sớm nhất có thể được. Ý tưởng là khai báo các đối tượng với cú pháp using và sau đó tạo một phạm vi hoạt động cho các đối tượng này trong khối được bao bởi dấu ({}). Khi khối phạm vi này kết thúc, thì phương thức Dispose() của đối tượng sẽ được gọi một cách tự động.
Ví dụ 4.6: Sử dụng chỉ dẫn using.
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
using System.Drawing;
class Tester
{
public static void Main()
{
using ( Font Afont = new Font(“Arial”,10.0f))
{
// Đoạn mã sử dụng AFont
.......
}// Trình biên dịch sẽ gọi Dispose để giải phóng AFont
Font TFont = new Font(“Tahoma”,12.0f);
using (TFont)
{
// Đoạn mã sử dụng TFont
.......
}// Trình biên dịch gọi Dispose để giải phóng TFont
}
}
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Trong phần khai báo đầu của ví dụ thì đối tượng Font được khai báo bên trong câu lệnh using. Khi câu lệnh using kết thúc, thì phương thức Dispose của đối tượng Font sẽ được gọi.
Còn trong phần khai báo thứ hai, một đối tượng Font được tạo bên ngoài câu lệnh using. Khi quyết định dùng đối tượng này ta đặt nó vào câu lệnh using. Và cũng tương tự như trên khi khối câu lệnh using thực hiện xong thì phương thức Dispose() của font được gọi.
Truyền tham số
Như đã thảo luận trong chương trước, tham số có kiểu dữ liệu là giá trị thì sẽ được truyền
giá trị vào cho phương thức. Điều này có nghĩa rằng khi một đối tượng có kiểu là giá trị được truyền vào cho một phương thức, thì có một bản sao chép đối tượng đó được tạo ra bên trong phương thức. Một khi phương thức được thực hiện xong thì đối tượng sao chép này sẽ được hủy. Tuy nhiên, đây chỉ là trường hợp bình thường, ngôn ngữ C# còn cung cấp khả năng cho phép ta truyền các đối tượng có kiểu giá trị dưới hình thức là tham chiếu. Ngôn ngữ C# đưa ra một bổ sung tham số là ref cho phép truyền các đối tượng giá trị vào trong phương thức theo kiểu tham chiếu. Và tham số bổ sung out trong trường hợp muốn truyền dưới dạng tham chiếu mà không cần phải khởi tạo giá trị ban đầu cho tham số truyền. Ngoài ra ngôn ngữ C# còn hỗ trợ bổ sung params cho phép phương thức chấp nhận nhiều số lượng các tham số.
Truyền tham chiếu
Những phương thức chỉ có thể trả về duy nhất một giá trị, mặc dù giá trị này có thể là một
tập hợp các giá trị. Nếu chúng ta muốn phương thức trả về nhiều hơn một giá trị thì cách thực hiện là tạo các tham số dưới hình thức tham chiếu. Khi đó trong phương thức ta sẽ xử lý và
gán các giá trị mới cho các tham số tham chiếu này, kết quả là sau khi phương thức thực hiện xong ta dùng các tham số truyền vào như là các kết quả trả về. Ví dụ 4.7 sau minh họa việc truyền tham số tham chiếu cho phương thức.
Ví dụ 4.7: Trả giá trị trả về thông qua tham số.
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
using System;
public class Time
{
public void DisplayCurrentTime()
{
Console.WriteLine(“{0}/{1}/{2}/ {3}:{4}:{5}”, Date, Month, Year, Hour, Minute, Second);
}
public int GetHour()
{
return Hour;
}
public void GetTime(int h, int m, int s)
{
h = Hour;
m = Minute;
s = Second;
}
public Time( System.DateTime dt)
{
Year = dt.Year; Month = dt.Month; Date = dt.Day; Hour = dt.Hour;
Minute = dt.Minute; Second = dt.Second;
}
private int Year; private int Month; private int Date; private int Hour; private int Minute; private int Second;
}
public class Tester
{
static void Main()
{
System.DateTime currentTime = System.DateTime.Now; Time t = new Time( currentTime);
t.DisplayCurrentTime();
int theHour = 0;
int theMinute = 0;
int theSecond = 0;
t.GetTime( theHour, theMinute, theSecond); System.Console.WriteLine(“Current time: {0}:{1}:{2}”,
theHour, theMinute, theSecond);
}
}
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Kết quả:
8/6/2002 14:15:20
Current time: 0:0:0
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Như ta thấy, kết quả xuất ra ở dòng cuối cùng là ba giá trị 0:0:0, rõ ràng phương thức GetTime() không thực hiện như mong muốn là gán giá trị Hour, Minute, Second cho các tham số truyền vào. Tức là ba tham số này được truyền vào dưới dạng giá trị. Do đó để thực hiện như mục đích của chúng ta là lấy các giá trị của Hour, Minute, Second thì phương thức GetTime() có ba tham số được truyền dưới dạng tham chiếu. Ta thực hiện như sau, đầu tiên, thêm là thêm khai báo ref vào trước các tham số trong phương thức GetTime():
public void GetTime( ref int h, ref int m, ref int s)
{
h = Hour;
m = Minute;
s = Second;
}
Điều thay đổi thứ hai là bổ sung cách gọi hàm GetTime để truyền các tham số dưới dạng tham chiếu như sau:
t.GetTime( ref theHour, ref theMinute, ref theSecond);
Nếu chúng ta không thực hiện bước thứ hai, tức là không đưa từ khóa ref khi gọi hàm thì trình biên dịch C# sẽ báo một lỗi rằng không thể chuyển tham số từ kiểu int sang kiểu ref int.
Cuối cùng khi biên dịch lại chương trình ta được kết quả đúng như yêu cầu. Bằng việc khai báo tham số tham chiếu, trình biên dịch sẽ truyền các tham số dưới dạng các tham chiếu, thay cho việc tạo ra một bản sao chép các tham số này. Khi đó các tham số bên trong GetTime() sẽ tham chiếu đến cùng biến đã được khai báo trong hàm Main(). Như vậy mọi sự thay đổi với các biến này điều có hiệu lực tương tự như là thay đổi trong hàm Main().
Tóm lại cơ chế truyền tham số dạng tham chiếu sẽ thực hiện trên chính đối tượng được đưa vào. Còn cơ chế truyền tham số giá trị thì sẽ tạo ra các bản sao các đối tượng được truyền vào, do đó mọi thay đổi bên trong phương thức không làm ảnh hưởng đến các đối tượng được truyền vào dưới dạng giá trị.
Truyền tham chiếu với biến chưa khởi tạo
Ngôn ngữ C# bắt buộc phải thực hiện một phép gán cho biến trước khi sử dụng, do đó khi khai báo một biến như kiểu cơ bản thì trước khi có lệnh nào sử dụng các biến này thì phải có lệnh thực hiện việc gán giá trị xác định cho biến. Như trong ví dụ 4.7 trên, nếu chúng ta không khởi tạo biến theHour, theMinute, và biến theSecond trước khi truyền như tham số vào phương thức GetTime() thì trình biên dịch sẽ báo lỗi. Nếu chúng ta sửa lại đoạn mã của
ví dụ 4.7 như sau: int theHour; int theMinute;
int theSecond;
t.GetTime( ref int theHour, ref int theMinute, ref int theSecond);
Việc sử dụng các đoạn lệnh trên không phải hoàn toàn vô lý vì mục đích của chúng ta là nhận các giá trị của đối tượng Time, việc khởi tạo giá trị của các biến đưa vào là không cần thiết. Tuy nhiên khi biên dịch với đoạn mã lệnh như trên sẽ được báo các lỗi sau:
Use of unassigned local variable ‘theHour’ Use of unassigned local variable ‘theMinute’
Use of unassigned local variable ‘theSecond’
Để mở rộng cho yêu cầu trong trường hợp này ngôn ngữ C# cung cấp thêm một bổ sung tham chiếu là out. Khi sử dụng tham chiếu out thì yêu cầu bắt buộc phải khởi tạo các tham số tham chiếu được bỏ qua. Như các tham số trong phương thức GetTime(), các tham số này không cung cấp bất cứ thông tin nào cho phương thức mà chỉ đơn giản là cơ chế nhận thông tin và đưa ra bên ngoài. Do vậy ta có thể đánh dấu tất cả các tham số tham chiếu này là out, khi đó ta sẽ giảm được công việc phải khởi tạo các biến này trước khi đưa vào phương thức. Lưu ý là bên trong phương thức có các tham số tham chiếu out thì các tham số này phải được gán giá trị trước khi trả về. Ta có một số thay đổi cho phương thức GetTime() như sau:
public void GetTime( out int h, out int m, out int s)
{
h = Hour;
m = Minute;
s = Second;
}
và cách gọi mới phương thức GetTime() trong Main():
t.GetTime( out theHour, out theMinute, out theSecond);
Tóm lại ta có các cách khai báo các tham số trong một phương thức như sau: kiểu dữ liệu giá
trị được truyền vào phương thức bằng giá trị. Sử dụng tham chiếu ref để truyền kiểu dữ liệu
giá trị vào phương thức dưới dạng tham chiếu, cách này cho phép vừa sử dụng và có khả năng thay đổi các tham số bên trong phương thức được gọi. Tham chiếu out được sử dụng chỉ để trả về giá trị từ một phương thức. Ví dụ 4.8 sau sử dụng ba kiểu tham số trên.
Ví dụ 4.8: Sử dụng tham số.
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
using System;
public class Time
{
public void DisplayCurrentTime()
{
Console.WriteLine(“{0}/{1}/{2} {3}:{4}:{5}”, Date, Month, Year, Hour, Minute, Second);
}
public int GetHour()
{
return Hour;
}
public void SetTime(int hr, out int min, ref int sec)
{
// Nếu số giây truyền vào >30 thì tăng số Minute và Second = 0
if ( sec >=30 )
{
Minute++; Second = 0;
}
Hour = hr; // thiết lập giá trị hr được truyền vào
// Trả về giá trị mới cho min và sec min = Minute;
sec = Second;
}
public Time( System.DateTime dt)
{
Year = dt.Year; Month = dt.Month; Date = dt.Day; Hour = dt.Hour;
Minute = dt.Minute; Second = dt.Second;
}
// biến thành viên private private int Year;
private int Month; private int Date; private int Hour; private int Minute; private int Second;
}
public class Tester
{
static void Main()
{
System.DateTime currentTime = System.DateTime.Now; Time t = new Time(currentTime);
t.DisplayCurrentTime();
int theHour = 3;
int theMinute;
int theSecond = 20;
t.SetTime( theHour, out theMinute, ref theSecond); Console.WriteLine(“The Minute is now: {0} and {1} seconds ”,
theMinute, theSecond);
theSecond = 45;
t.SetTime( theHour, out theMinute, ref theSecond); Console.WriteLine(“The Minute is now: {0} and {1} seconds”,
theMinute, theSecond);
}
}
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Kết quả:
8/6/2002 15:35:24
The Minute is now: 35 and 24 seconds
The Minute is now: 36 and 0 seconds
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Phương thức SetTime trên đã minh họa việc sử dụng ba kiểu truyền tham số vào một phương thức. Tham số thứ nhất theHour được truyền vào dạng giá trị, mục đích của tham số này là để thiết lập giá trị cho biến thành viên Hour và tham số này không được sử dụng để về bất cứ giá trị nào.
Tham số thứ hai là theMinute được truyền vào phương thức chỉ để nhận giá trị trả về của biến thành viên Minute, do đó tham số này được khai báo với từ khóa out.
Cuối cùng tham số theSecond được truyền vào với khai báo ref, biến tham số này vừa dùng
để thiết lập giá trị trong phương thức. Nếu theSecond lớn hơn 30 thì giá trị của biến thành viên Minute tăng thêm một đơn vị và biến thành viên Second được thiết lập về 0. Sau cùng thì theSecond được gán giá trị của biến thành viên Second và được trả về.
Do hai biến theHour và theSecond được sử dụng trong phương thức SetTime nên phải được khởi tạo trước khi truyền vào phương thức. Còn với biến theMinute thì không cần thiết vì nó không được sử dụng trong phương thức mà chỉ nhận giá trị trả về.
Nạp chồng phương thức
Thông thường khi xây dựng các lớp, ta có mong muốn là tạo ra nhiều hàm có cùng tên. Cũng như hầu hết trong các ví dụ trước thì các lớp điều có nhiều hơn một phương thức khởi dựng. Như trong lớp Time có các phương thức khởi dựng nhận các tham số khác nhau, như tham số là đối tượng DateTime, hay tham số có thể được tùy chọn để thiết lập các giá trị của các biến thành viên thông qua các tham số nguyên. Tóm lại ta có thể xây dựng nhiều các phương thức cùng tên nhưng nhận các tham số khác nhau. Chức năng này được gọi là nạp chồng phương thức.
Một ký hiệu (signature) của một phương thức được định nghĩa như tên của phương thức cùng
với danh sách tham số của phương thức. Hai phương thức khác nhau khi ký hiệu của chúng khác là khác nhau tức là khác nhau khi tên phương thức khác nhau hay danh sách tham số khác nhau. Danh sách tham số được xem là khác nhau bởi số lượng các tham số hoặc là kiểu dữ liệu của tham số. Ví dụ đoạn mã sau, phương thức thứ nhất khác phương thức thứ hai do số lượng tham số khác nhau. Phương thức thứ hai khác phương thức thứ ba do kiểu dữ liệu tham số khác nhau:
void myMethod( int p1 );
void myMethod( int p1, int p2 );
void myMethod( int p1, string p2 );
Một lớp có thể có bất cứ số lượng phương thức nào, nhưng mỗi phương thức trong lớp phải
có ký hiệu khác với tất cả các phương thức thành viên còn lại của lớp.
Ví dụ 4.9 minh họa lớp Time có hai phương thức khởi dựng, một phương thức nhận tham số
là một đối tượng DateTime còn phương thức thứ hai thì nhận sáu tham số nguyên.
Ví dụ 4.9: Minh họa nạp chồng phương thức khởi dựng.
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
using System;
public class Time
{
public void DisplayCurrentTime()
{
Console.WriteLine(“{0}/{1}/{2} {3}:{4}:{5}”, Date, Month, Year, Hour, Minute, Second);
}
public Time( System.DateTime dt)
{
Year = dt.Year; Month = dt.Month; Date = dt.Day; Hour = dt.Hour;
Minute = dt.Minute; Second = dt.Second;
}
public Time(int Year, int Month, int Date, int Hour, int Minute, int Second)
{
this.Year = Year; this.Month = Month; this.Date = Date; this.Hour = Hour; this.Minute = Minute; this.Second = Second;
}
// Biến thành viên private private int Year;
private int Month; private int Date; private int Hour; private int Minute; private int Second;
}
public class Tester
{
static void Main()
{
System.DateTime currentTime = System.DateTime.Now; Time t1 = new Time( currentTime); t1.DisplayCurrentTime();
Time t2 = new Time(2002,6,8,18,15,20);
t2.DisplayCurrentTime();
}
}
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Kết quả:
2/1/2002 17:50:17
8/6/2002 18:15:20
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Như chúng ta thấy, lớp Time trong ví dụ minh họa 4.9 có hai phương thức khởi dựng. Nếu hai phương thức có cùng ký hiệu thì trình biên dịch sẽ không thể biết được gọi phương thức nào khi khởi tạo hai đối tượng là t1 và t2. Tuy nhiên, ký hiệu của hai phương thức này khác nhau vì tham số truyền vào khác nhau, do đó trình biên dịch sẽ xác định được phương thức nào được gọi dựa vào các tham số.
Khi thực hiện nạp chồng một phương thức, bắt buộc chúng ta phải thay đổi ký hiệu của phương thức, số tham số, hay kiểu dữ liệu của tham số. Chúng ta cũng có thể toàn quyền thay đổi giá trị trả về, nhưng đây là tùy chọn. Nếu chỉ thay đổi giá trị trả về thì không phải nạp chồng phương thức mà khi đó hai phương thức khác nhau, và nếu tạo ra hai phương thức cùng ký hiệu nhưng khác nhau kiểu giá trị trả về sẽ tạo ra một lỗi biên dịch.
Ví dụ 4.10: Nạp chồng phương thức.
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
using System;
public class Tester
{
private int Triple( int val)
{
return 3*val;
}
private long Triple(long val)
{
return 3*val;
}
public void Test()
{
int x = 5;
int y = Triple(x);
Console.WriteLine(“x: {0} y: {1}”, x, y);
long lx = 10;
long ly = Triple(lx);
Console.WriteLine(“lx: {0} ly:{1}”, lx, ly);
}
static void Main()
{
Tester t = new Tester();
t.Test();
}
}
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Kết quả: x: 5 y: 15 lx: 10 ly:30
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Trong ví dụ này, lớp Tester nạp chồng hai phương thức Triple(), một phương thức nhận tham
số nguyên int, phương thức còn lại nhận tham số là số nguyên long. Kiểu giá trị trả về của hai phương thức khác nhau, mặc dù điều này không đòi hỏi nhưng rất thích hợp trong trường hợp này.
Đóng gói dữ liệu với thành phần thuộc tính
Thuộc tính là khái niệm cho phép truy cập trạng thái của lớp thay vì thông qua truy cập trực tiếp các biến thành viên, nó sẽ đựơc thay thế bằng việc thực thi truy cập thông qua phương thức của lớp.
Đây thật sự là một điều lý tưởng. Các thành phần bên ngoài (client) muốn truy cập trạng thái của một đối tượng và không muốn làm việc với những phương thức. Tuy nhiên, người thiết kế lớp muốn dấu trạng thái bên trong của lớp mà anh ta xây dựng, và cung cấp một cách gián tiếp thông qua một phương thức.
Thuộc tính là một đặc tính mới được giới thiệu trong ngôn ngữ C#. Đặc tính này cung cấp khả năng bảo vệ các trường dữ liệu bên trong một lớp bằng việc đọc và viết chúng thông qua thuộc tính. Trong ngôn ngữ khác, điều này có thể được thực hiện thông qua việc tạo các phương thức lấy dữ liệu (getter method) và phương thức thiết lập dữ liệu (setter method).
Thuộc tính được thiết kế nhắm vào hai mục đích: cung cấp một giao diện đơn cho phép truy cập các biến thành viên, Tuy nhiên cách thức thực thi truy cập giống như phương thức trong đó các dữ liệu được che dấu, đảm bảo cho yêu cầu thiết kế hướng đối tượng. Để hiểu rõ
đặc tính này ta sẽ xem ví dụ 4.11 bên dưới:
Ví dụ 4.11: Sử dụng thuộc tính.
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
using System;
public class Time
{
public void DisplayCurrentTime()
{
Console.WriteLine(“Time\t: {0}/{1}/{2} {3}:{4}:{5}”, date, month, year, hour, minute, second);
}
public Time( System.DateTime dt)
{
year = dt.Year; month = dt.Month; date = dt.Day;
hour = dt.Hour; minute = dt.Minute; second = dt.Second;
}
public int Hour
{
get
{
}
set
{
}
}
return hour;
hour = value;
// Biến thành viên private private int year;
private int month;
private int date;
private int hour;
private int minute;
private int second;
}
public class Tester
{
static void Main()
{
System.DateTime currentTime = System.DateTime.Now; Time t = new Time( currentTime ); t.DisplayCurrentTime();
// Lấy dữ liệu từ thuộc tính Hour int theHour = t.Hour;
Console.WriteLine(“ Retrieved the hour: {0}”, theHour);
theHour++;
t.Hour = theHour;
Console.WriteLine(“Updated the hour: {0}”, theHour);
}
}
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Kết quả:
Time : 2/1/2003 17:55:1
Retrieved the hour: 17
Updated the hour: 18
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Để khai báo thuộc tính, đầu tiên là khai báo tên thuộc tính để truy cập, tiếp theo là phần thân định nghĩa thuộc tính nằm trong cập dấu ({}). Bên trong thân của thuộc tính là khai báo hai
bộ truy cập lấy và thiết lập dữ liệu:
public int Hour
{
get
{
}
set
{
}
return hour;
hour = value;
}
Mỗi bộ truy cập được khai báo riêng biệt để làm hai công việc khác nhau là lấy hay thiết lập
giá trị cho thuộc tính. Giá trị thuộc tính có thể được lưu trong cơ sở dữ liệu, khi đó trong phần thân của bộ truy cập sẽ thực hiện các công việc tương tác với cơ sở dữ lịêu. Hoặc là giá trị thuộc tính được lưu trữ trong các biến thành viên của lớp như trong ví dụ:
private int hour;
Truy cập lấy dữ liệu (get accessor)
Phần khai báo tương tự như một phương thức của lớp dùng để trả về một đối tượng có kiểu dữ liệu của thuộc tính. Trong ví dụ trên, bộ truy cập lấy dữ liệu get của thuộc tính Hour cũng tương tự như một phương thức trả về một giá trị int. Nó trả về giá trị của biến thành viên hour nơi mà giá trị của thuộc tính Hour lưu trữ:
get
{
return hour;
}
Trong ví dụ này, một biến thành viên cục bộ được trả về, nhưng nó cũng có thể truy cập dễ dàng một giá trị nguyên từ cơ sở dữ lịêu, hay thực hiện việc tính toán tùy ý.
Bất cứ khi nào chúng ta tham chiếu đến một thuộc tính hay là gán giá trị thuộc tính cho một biến thì bộ truy cập lấy dữ liệu get sẽ được thực hiện để đọc giá trị của thuộc tính:
Time t = new Time( currentTime );
int theHour = t.Hour;
Khi lệnh thứ hai được thực hiện thì giá trị của thuộc tính sẽ được trả về, tức là bộ truy cập lấy dữ lịêu get sẽ được thực hiện và kết quả là giá trị của thuộc tính được gán cho biến cục bộ theHour.
Bộ truy cập thiết lập dữ liệu ( set accessor)
Bộ truy cập này sẽ thiết lập một giá trị mới cho thuộc tính và tương tự như một phương thức trả về một giá trị void. Khi định nghĩa bộ truy cập thiết lập dữ lịêu chúng ta phải sử dụng từ khóa value để đại diện cho tham số được truyền vào và được lưu trữ bởi thuộc tính:
set
{
hour = value;
}
Như đã nói trước, do ta đang khai báo thuộc tính lưu trữ dưới dạng biến thành viên nên trong phần thân của bộ truy cập ta chỉ sử dụng biến thành viên mà thôi. Bộ truy cập thiết lập hoàn toàn cho phép chúng ta có thể viết giá trị vào trong cơ sở dữ lịêu hay cập nhật bất cứ biến thành viên nào khác của lớp nếu cần thiết.
Khi chúng ta gán một giá trị cho thuộc tính thì bộ truy cập thiết lập dữ liệu set sẽ được tự động thực hiện và một tham số ngầm định sẽ được tạo ra để lưu giá trị mà ta muốn gán:
theHour++;
t.Hour = theHour;
Lợi ích của hướng tiếp cận này cho phép các thành phần bên ngoài (client) có thể tương tácvới thuộc tính một cách trực tiếp, mà không phải hy sinh việc che dấu dữ lịêu cũng như đặc tính đóng gói dữ lịêu trong thiết kế hướng đối tượng.
Thuộc tính chỉ đọc
Giả sử chúng ta muốn tạo một phiên bản khác cho lớp Time cung cấp một số giá trị static để hiển thị ngày và giờ hiện hành. Ví dụ 4.12 minh họa cho cách tiếp cận này.
Ví dụ 4.12: Sử dụng thuộc tính hằng static.
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
using System;
public class RightNow
{
// Định nghĩa bộ khởi tạo static cho các biến static static RightNow()
{
System.DateTime dt = System.DateTime.Now; Year = dt.Year;
Month = dt.Month; Date = dt.Day; Hour = dt.Hour;
Minute = dt.Minute; Second = dt.Second;
}
// Biến thành viên static public static int Year; public static int Month; public static int Date; public static int Hour; public static int Minute; public static int Second;
}
public class Tester
{
static void Main()
{
Console.WriteLine(“This year: {0}”, RightNow.Year.ToString());
RightNow.Year = 2003; Console.WriteLine(“This year: {0}”,
RightNow.Year.ToString());
}
}
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Kết quả:
This year: 2002
This year: 2003
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Đoạn chương trình trên hoạt động tốt, tuy nhiên cho đến khi có một ai đó thay đổi giá trị của biến thành viên này. Như ta thấy, biến thành Year trên đã được thay đổi đến 2003. Điều này thực sự không như mong muốn của chúng ta.
Chúng ta muốn đánh dấu các thuộc tính tĩnh này không được thay đổi. Nhưng khai báo hằng cũng không được vì biến tĩnh không được khởi tạo cho đến khi phương thức khởi dựng static được thi hành. Do vậy C# cung cấp thêm từ khóa readonly phục vụ chính xác cho mục đich trên. Với ví dụ trên ta có cách khai báo lại như sau:
public static readonly int Year; public static readonly int Month; public static readonly int Date; public static readonly int Hour; public static readonly int Minute; public static readonly int Second;
Khi đó ta phải bỏ lệnh gán biến thành viên Year, vì nếu không sẽ bị báo lỗi:
// RightNow.Year = 2003; // error
Chương trình sau khi biên dịch và thực hiện như mục đích của chúng ta.
Câu hỏi và trả lời
Câuhỏi 1: Có phải chúng ta chỉ nên sử dụng lớp với các dữ liệu thành viên?
Trả lời 1: Nói chung là chúng ta không nên sử dụng lớp chỉ với dữ liệu thành viên. Ý nghĩa của môt lớp hay của lập trình hướng đối tượng là khả năng đóng gói các chức năng và dữ liệu vào trong một gói đơn.
Câu hỏi 2: Có phải tất cả những dữ liệu thành viên luôn luôn được khai báo là public để bên ngoài có thể truy cập chúng?
Trả lời 2: Nói chung là không. Do vấn đề che dấu dữ liệu trong lập trình hướng đối tượng,
xu hướng là dữ liệu bên trong chỉ nên dùng cho các phương thức thành viên. Tuy nhiên, như chúng ta đã biết khái niệm thuộc tính cho phép các biến thành viên được truy cập từ bên ngoài thông qua hình thức như là phương thức.
Câuhỏi 3: Có phải có rất nhiều lớp được xây dựng sẵn và tôi có thể tìm chúng ở đâu?
Trả lời 3: Microsoft cung cấp rất nhiều các lớp gọi là các lớp cơ sở .NET. Những lớp này được tổ chức bên trong các namespace. Chúng ta có thể tìm tài liệu về các lớp này trong thư viện trực tuyến của Microsoft. Và một số lớp thường sử dụng cũng được trình bày lần lượt trong các ví dụ của giáo trình này.
Câuhỏi 4: Sự khác nhau giữa tham số (parameter) và đối mục (argument)?
Trả lời 4: Tham số được định nghĩa là những thứ được truyền vào trong một phương thức. Một tham số xuất hiện với định nghĩa của phương thức ở đầu phương thức. Một đối mục là giá trị được truyền vào phương thức. Chúng ta truyền những đối mục vào phương thức phù hợp với những tham số đã khai báo của phương thức.
Câuhỏi 5: Chúng ta có thể tạo phương thức bên ngoài của lớp hay không?
Trả lời 5: Mặc dù trong những ngôn ngữ khác, chúng ta có thể tạo các phương thức bên ngoài của lớp. Nhưng trong C# thì không, C# là hướng đối tượng, do vậy tất cả các mã nguồn phải được đặt bên trong một lớp.
Câu hỏi 6: Có phải những phương thức và lớp trong C# hoạt động tương tự như trong các ngôn ngữ khác như C++ hay Java?
Trả lời 6: Trong hầu hết các phần thì chúng tương tự như nhau. Tuy nhiên, mỗi ngôn ngữ cũng có những khác biệt riêng. Một ví dụ sự khác nhau là C# không cho phép tham số mặc định bên trong một phương thức. Trong ngôn ngữ C++ thì chúng ta có thể khai báo các tham số mặc định lúc định nghĩa phương thức và khi gọi phương thức thì có thể không cần truyền giá trị vào, phương thức sẽ dùng giá trị mặc định. Trong C# thì không được phép. Nói chung là còn nhiều sự khác nhau nữa, nhưng xin dành cho bạn đọc tự tìm hiểu.
Câu hỏi 7: Phương thức tĩnh có thể truy cập được thành viên nào và không truy cập được thành viên nào trong một lớp?
Trả lời 7: Phương thức tĩnh chỉ truy cập được các thành viên tĩnh trong một lớp.
Câu hỏi thêm
Câu hỏi 1: Sự khác nhau giữa thành viên được khai báo là public và các thành viên không được khai báo là public?
Câuhỏi 2: Từ khoá nào được sử dụng trong việc thực thi thuộc tính của lớp?
Câuhỏi 3: Những kiểu dữ liệu nào được trả về từ phương thức?
Câu hỏi 4: Sự khác nhau giữa truyền biến tham chiếu và truyền biến tham trị vào một phương thức?
Câuhỏi 5: Làm sao truyền tham chiếu với biến kiểu giá trị vào trong một phương thức?
Câuhỏi 6: Khi nào thì phương thức khởi dựng được gọi?
Câuhỏi 7: Phương thức khởi dựng tĩnh được gọi khi nào?
Câuhỏi 8: Có thể truyền biến chưa khởi tạo vào một hàm được không?
Câuhỏi 9: Sự khác nhau giữa một lớp và một đối tượng của lớp?
Câu hỏi 10: Thành viên nào trong một lớp có thể được truy cập mà không phải tạo thể hiện của lớp?
Câuhỏi 11: Lớp mà chúng ta xây dựng thuộc kiểu dữ liệu nào?
Câuhỏi 12: Từ khóa this được dùng làm gì trong một lớp?
Bài tập
Bài tập 1: Xây dựng một lớp đường tròn lưu giữ bán kính và tâm của đường tròn. Tạo các phương thức để tính chu vi, diện tích của đường tròn.
Bài tập2: Thêm thuộc tính BanKinh vào lớp được tạo ra từ bài tập 1.
Bài tập 3: Tạo ra một lớp lưu trữ giá trị nguyên tên myNumber. Tạo thuộc tính cho thành viên này. Khi số được lưu trữ thì nhân cho 100. Và khi số được truy cập thì chia cho 100.
Bài tập 4: Chương trình sau có lỗi. Hãy sửa lỗi của chương trình và biên dịch chương trình. Dòng lệnh nào gây ra lỗi?
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
using System;
using System.Console;
class VD1
{
public string first;
}
class Tester
{
public static void
{
VD1 vd = new VD1(); Write(“Nhap vao mot chuoi: ”); vd.first = ReadLine();
Write(“Chuoi nhap vao: {0}”, vd.first);
}
}
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Bài tập 5: Chương trình sau có lỗi. Hãy sửa lỗi của chương trình và biên dịch chương trình. Dòng lệnh nào gây ra lỗi?
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
class Class1
{
public static void GetNumber(ref int x, ref int y)
{
x = 5;
y = 10;
}
public static void
{
int a = 0, b = 0; GetNumber(a, b);
System.Console.WriteLine(“a = {0} \nb = {1}”, a, b);
}
}
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Câuhỏi 6: Chương trình sau đây có lỗi. Hãy sửa lỗi và cho biết lệnh nào phát sinh lỗi?
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Class Tester
{
public static void Main()
{
Display();
}
public static void Display()
{
System.Console.WriteLine(“Hello!”);
return 0;
}
}
—–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—-–—
Câu hỏi 7: Viết lớp giải phương trình bậc hai. Lớp này có các thuộc tính a, b, c và nghiệm
x1, x2. Hãy xây dựng theo hướng đối tượng lớp trên. Lớp cho phép bên ngoài xem được các nghiệm của phương trình và cho phép thiết lập hay xem các giá trị a, b, c.
Hết chương 4.