Câu lạc bộ Khoa học - THPT Chuyên Hồng Phong TPHCM LHPSC
Lập trình C# - Phần 3: Giới thiệu về Lớp Trang 1
Lập tnh C#
Dịch từ cuốn sách Beginning C Sharp Game Programming
Phần 3: Giới thiệu về Lớp
Gía trị với Tham chiếu
những cách khác nhau để một trình biên dịch nói về dữ liệu, và C# hai cách. Mọi kiểu dữ liu
trong C# đều rơi vào một trong hai loại:
Kiểu giá trị (Value type)
Kiểu tham chiếu (Reference type)
Tôi sẽ giải thích các điểm khác nhau của mỗi loi trong những mục sau.
Kiểu dữ liệu
Một kiểu giá trthường một miếng nhỏ của dữ liệu mà hệ thống dành rất ít thời gian đsắp xếp.
Bạn đã sử dụng kiểu giá trtrong Phần 2 với tất cả các kiểu dữ liệu số được xây dựng sẵn. Mọi thứ
được liệt kê trong bảng 2.1 – như là int, float và vâng vâng – là một kiểu giá trị.
Ghi chú
Kiểu giá trđược tạo trên ngăn xếp hệ thống (system stack). Bạn không cần thiết để biết nó là
gì, nhưng nếu bạn cảm thấy thú vị, tôi khuyên bạn rằng nên tự nghiên cứu nó. Chủ đề này
vượt ra ngoài phạm vi của quyển ch này, vì vy i không đủ chỗ để giải thích đây,
nhưng nó sgiúp bạn hiểu chính xác m sao máy vi nh hoạt động, sẽ ảnh hưởng đến
việc tạo chương trình của bạn nhanh hơn và hiệu quả hơn.
Kiểu giá trị đơn gin và rất minh bạch để sử dụng, như là đoạn mã sau:
int x = 10, y = 20;
x = y; // Giá tr ca y được chép vào x
y = 10; // y được gán bng 10
Bên cạnh những kiểu đượcdựng sẵn, cấu trúc (structure) cũng một kiểu giá trị, nhưng tôi s đề cập
về sau trong phần này.
Kiểu tham chiếu
Kiểu tham chiếu hoàn toàn khác biệt so với kiểu giá trị. Lớp, khác với cấu trúc, luôn luôn là kiểu tham
chiếu. Kiểu tham chiếu, thay vì lưu dữ liệu một cách trực tiếp, nó lại lưu một địa chỉ, và địa chỉ đó trỏ
tới dữ liệu thật trong nơi nào đó của máy vi tính. Xem hình 3.1.
Khai báo một Kiểu Tham chiếu
Một trong những điểm khác nhau lớn nhất giữa kiểu giá trị và tham chiếu là cách mà bạn khai báo cho
nó. Một kiểu tham chiếu phải được tạo bởi từ khóa new (giả sử như chúng ta có một lớp tên Foo):
Foo x = new Foo();
Câu lạc bộ Khoa học - THPT Chuyên Hồng Phong TPHCM LHPSC
Lập trình C# - Phần 3: Giới thiệu về Lớp Trang 2
Trông vẻ nhiều việc cần làm, nhưng bạn s quen với nó. bản, đoạn mã thực hiện hai công việc.
Đó là:
1. Tạo một kiểu tham chiếu mới tên x, và
2. Tạo một đối tượng Foo mới trong đống dữ liệu và trỏ x đến đó.
Hình 3.1 Kiểu giá trị được lưu trực tiếp, trong khi kiểu tham chiếu lưu một địa chỉ trỏ tới dữ liệu thật.
Ghi chú
Đống dữ liệu (Heap) mt phần khác của máy vi nh để lưu trữ dữ liệu. Tôi không đủ
chỗ để giải thích nó ở đây; đó chỉ là một vài thứ khác bạn nên nghiên cứu cho riêng bạn nên
bạn cảm thấy thú vị.
Đương nhiên, bạn không cần phải thực hiện chúng một lượt. Bạn có thể dễ dàng tách chúng ra như thế
này:
Foo x;
x = new Foo();
tùy thuộc vào bạn.
Chơi đùa với các Tham chiếu
Và bây gilúc đchơi đùa với các tham chiếu, thứ mà bạn chưa gặp trước đây. Không may
cho bạn, các tham chiếu không hoạt động theo cách của các kiểu dữ liệu, mà nó có thể gây bối rối một
chút.
Đây là phần mà các tham chiếu có vẻ hơi khó hiểu một chút. Bạn hoàn toàn phải nhớ điều này mọi lúc
môi nơi khi sử dụng các tham chiếu, còn không chươn trình của bạn sẽ trở thành một mớ hỗn độn.
dụ, hãy thử đoán xem đoạn mã này làm gì:
Foo x = new Foo();
Foo y = new Foo();
y = x;
// Thay đi y đây
Bạn nghĩ sau khi đọan mã này thực thi, x giữ nguyên trạng thái ban đầu y thay đổi, đúng
không ? Sai !!! Chúng cùng thay đổi. Hãy nghe tôi – có một chút khó khăn đ nhìn nhn nó, nhưng nó
nghĩa. Sơ đồ có thể giúp, nhìn hình 3.2.
int x
10
Int32 y
địa chỉ của
dữ liệu
10
Ki
u giá tr
Câu lạc bộ Khoa học - THPT Chuyên Hồng Phong TPHCM LHPSC
Lập trình C# - Phần 3: Giới thiệu về Lớp Trang 3
Hình 3.2 Gán x cho y làm y trỏ tới dữ liệu của x, và không thực sự sao chép giá trị như bạn mong đợi.
Cơ bản, dòng này thật sự đã làm ri tung mọi thứ lên:
y = x;
Điều thật sự đã hoàn thành? Bạn muốn sao chép giá trị từ x sang y, nhưng điều đó đã không xảy ra.
Thay vào đó, chúng là những kiểu tham chiếu, máy vi tính s try o cùng dữ liệu x trỏ vào.
Vì vậy x y bây giờ trỏ đến cùng một dữ liệu trong bộ nhớ, thực hiện thao c nào trên y sẽ m
giống vậy đối với x.
Bộ thu gom rác
Trong dụ nh 3.2, bạn thể ghi nhn rằng y đã giành lấy một vùng nhớ, và sau đó bị bỏ qua
khi gán x cho y. Vậy điều gì sẽ xảy ra đối với vùng nhớ mà y trỏ tới trước đó?
Trong những ngôn ngữ cũ, như C, vùng nhđó sbị mất mãi mãi. Bạn sẽ tạo cái gọi là con trỏ treo
(dangling pointer), các con trgiống các tham chiếu; máy vi tính biết rằng vùng nhớ đã được sử
dụng, nhưng chương trình của bạn sẽ quên nó ở đâu, và bạn sẽ không bao giờ có thể lấy lại vùng nh
đó được nữa trừ khi bạn tắt chương trình đi.
C# giải quyết được vấn đề này bởi bộ thu gom rác. Mỗi lần bạn tạo một mnh dữ liu trong C#, .NET
runtime stheo dõi chương trình của bạn trđến dữ liệu đó bao nhiêu lần, và nếu số lần đó về 0, thì
bộ thu gom rác sẽ phát hiệngiải phóng vùng nhớ đó để sử dụng cho thứ khác.
Không thể có rò rỉ bộ nhớ trong C#.
null
một giá trđặc biệt mà bạn thể sử dụng với kiểm tham chiếu; gọi null. Giá trnull
nghĩa chẳng gì”. Nếu bạn đặt một tham chiếu đến null, t bạn đang nói với máy vi tính rằng
tham chiếu đó chẳng trtới đâu. Những ngôn ngữ hơn sử dng giá trị 0 để biểu thị nó, nhưng
null thì dễ đọc hơn.
x
y
Foo x = new Foo();
Foo y = new Foo();
dữ liệu
dữ liệu
x
y
y = x;
dữ liệu
dữ liệu
x
y
// Thay đi y
dữ liệu
thay đổi
dữ liệu
Câu lạc bộ Khoa học - THPT Chuyên Hồng Phong TPHCM LHPSC
Lập trình C# - Phần 3: Giới thiệu về Lớp Trang 4
Cơ bản về Cấu trúc (Structure) và Lớp (Class)
Trong những ngày đầu của ngôn ngữ máy tính, ngôn ngữ lập trình khá đơn gin, và bạn có thể chỉ tạo
được một số lượng biến giới hạn. Điều này rõ ràng làm cho chương trình rất hạn chế khá tồi tệ. Ví
dụ, bạn sẽ tạo ra chương trình như thế này trong những ngôn ngữ lập trình cũ:
int SpaceshipArmor;
int SpaceshipPower;
int SpaceshipFuel;
int EnemyArmor;
int EnemyPower;
int EnemyFuel;
Điều này sẽ là rối tung mọi thứ rất nhanh, và làm cho nó rất khó để quản lý mã của bạn.
Sử dụng lớp và cấu trúc sẽ m cho cuộc sống của bạn đơn giản hơn khi đóng gói c dữ liệu đó vào
những gói dữ liệu “rất dễ sử dụng”.
Tạo ra Lớp và Cấu trúc
Về bản, ý tưởng đằng sau lớp cấu trúc tạo ra kiểu đối tượng cho riêng bạn bằng những đối
tượng đã sẵn. Một cấu trúc (structure) một kiểu dữ liệu mà có thể giữ các dữ liệu khác bên
trong, cho pp bạn y dựng kiểu dữ liệu của riêng bạn. Ví dụ, đây một cấu trúc tả một đối
tượng tàu vũ trụ đơn gin trong C#:
struct Spaceship
{
public int fuel;
public int armor;
public int power;
}
Ghi chú
Từ khóa public nói cho trình biên dịch rằng bất kỳ m nào ở bất cứ đâu đầu có thể truy cập
dữ liệu bên trong một cấu trúc. Bạn không phải lo vviệc này; tôi sẽ đi sâu hơn vào điều này
trong những mục sau. Nếu public bbỏ đi, thì máy vi tính sẽ xem rằng bạn không muốn thứ gì
bên ngoài truy cập nó.
Ghi chú
Để tạo một lớp, đơn giản hãy thay từ khóa struct thành class trong dụ trước.
Và bậy giờ, bên trong chương trình của bạn, bạn có thể tạo biến tàu vũ trụ cho riêng bạn:
Spaceship player;
Spaceship enemy;
player.fuel = 100;
enemy.fuel = 100;
Điều này đơn gin, phải không?
Sự khác nhau giữa Cấu trúc và Lớp
Trong C#,một vài điu khác nhau cơ bản giữa cấu trúc và lớp. Cấu trúc thường đơn gin và không
nhiều thứ phức tạp bên trong chúng. Cấu trúc thường nhỏ hơn lớp, C# luôn tạo cấu trúc kiểu
giá trị (nghĩa là chúng sẽ luôn luôn tạo trong ngăn xếp).
Câu lạc bộ Khoa học - THPT Chuyên Hồng Phong TPHCM LHPSC
Lập trình C# - Phần 3: Giới thiệu về Lớp Trang 5
Lớp, khác với cấu trúc, luôn luôn kiểu tham chiếu, luôn được tạo trên một đống (heap), thay vì
trên ngăn xếp. Lớp nhiều thứ mà cấu trúc không có, tôi sgiải thích nhng thnày khi chúng ta
gặp nó.
Đưa Hàm (Function) và Lớp và Cấu trúc
Lớp cấu trúc không chỉ nh năng lưu trữ dữ liệu, mà chúng còn có thể thực hiện một vài phép
tính nữa, nếu bạn cho chúng khả năng đó. dụ, bạn muốn đặt lại mọi dữ liệu của tàu thành 100 một
cách nhanh chóng; nếu khôngmột hàm, nó trông giống đoạn mã sau:
player.fuel = 100;
player.armor = 100;
player.power = 100;
Rõ ràng, điều này không phải là thứ bạn chỉ m một lần, vì vậy tại sao thay vào đó không cho nó vào
một hàm, nên lớp Spaceship sẽ trông như thế này (phần mới được tô đậm):
struct Spaceship
{
public int fuel;
public int armor;
public int power;
public void Recharge()
{
fuel = 100;
armor = 100;
power = 100;
}
}
Bây giờ bạn có thể chỉ cần gọi hàm Recharge trên tàu vũ trụ của bạn khi bạn muốn đặt lại tất cả cái
biến:
player.Recharge();
Trả về giá trị
Các hàm không chỉ trhực hiện các tác vụ, mà chúng còn thể trvề giá trị. dụ, hãy nói bạn
một con u trụ; bạn biết nhiên liệu và năng lượng có, nhưng bạn không thực sự chc chắn về
thời gian mà ng lượng cung cấp sẽ hết. Để tính toán nó, bạn cần thiết lập một công thức hãy nói
rằng mỗi đơn vị năng lượng duy trì được hai giờ; và bạn muốn tìm xem còn bao nhiêu thời gian với số
năng lượng còn lại, bạn sẽm điều gì đó như thế này:
int hoursleft = player.power * 2;
Đó là một cách để giải quyết vấn đ, nhưng đó không thực sự một giải pháp tốt. Về sau, bạn có thể
đề nghị mỗi đơn vị năng lượng tương đương với ba giờ thay vì hai. Đ thay đổi điều này, bạn phải
hết lại mã của bạn và tìm xem chỗ nào bạn sử dụng số 2 và thay bằng số 3. Không vui chút nào.
Vì vy, hãy đưng quá trình này vào một m!
int HoursofPowerLeft()
{ return power * 2;
}