CẤU TRÚC · Định nghĩa một cấu trúc · Tạo cấu trúc · Cấu trúc là một kiểu giá trị · Gọi bộ khởi dựng mặc định · Tạo cấu trúc không gọi new · Câu hỏi & bài tập Cấu trúc là kiểu dữ liệu đơn
Trang 1CẤU TRÚC
· Định nghĩa một cấu trúc
· Tạo cấu trúc
· Cấu trúc là một kiểu giá trị
· Gọi bộ khởi dựng mặc định
· Tạo cấu trúc không gọi new
· Câu hỏi & bài tập
Cấu trúc là kiểu dữ liệu đơn giản do người dùng định nghĩa, kích thước nhỏ dùng để thay thế cho lớp Những cấu trúc thì tương tự như lớp cũng chứa các phương thức, những thuộc tính, các trường, các toán tử, các kiểu dữ liệu lồng bên trong và bộ chỉ mục (indexer)
Có một số sự khác nhau quan trọng giữa những lớp và cấu trúc Ví dụ, cấu trúc thì không hỗ
trợ kế thừa và bộ hủy giống như kiểu lớp Một điều quan trọng nhất là trong khi lớp là kiểu
dữ liệu tham chiếu, thì cấu trúc là kiểu dữ lịêu giá trị (Chương 3 đã thảo luận về kiểu
dữ liệu tham chiếu và kiểu dữ liệu giá trị) Do đó cấu trúc thường dùng để thể hiển các đối tượng không đòi hỏi một ngữ nghĩa tham chiếu, hay một lớp nhỏ mà khi đặt vào trong stack thì có lợi hơn là đặt trong bộ nhớ heap
Một sự nhận xét được rút ra là chúng ta chỉ nên sử dụng những cấu trúc chỉ với những kiểu
dữ liệu nhỏ, và những hành vi hay thuộc tính của nó giống như các kiểu dữ liệu được xây dựng sẵn
Cấu trúc có hiệu quả khi chúng ta sử dụng chúng trong mảng bộ nhớ (Chương 9) Tuy nhiên, cấu trúc sẽ kém hiệu quả khi chúng ta sử dụng dạng tập hợp (collections) Tập hợp được xây dựng hướng tới các kiểu dữ liệu tham chiếu
Trong chương này chúng ta sẽ tìm hiểu các định nghĩa và làm việc với kiểu cấu trúc và cách
sử dụng bộ khởi dựng để khởi tạo những giá trị của cấu trúc
Định nghĩa một cấu trúc
Cú pháp để khai báo một cấu trúc cũng tương tự như cách khai báo một lớp:
[thuộc tính] [bổ sung truy cập] struct <tên cấu trúc> [: danh sách giao diện]
{
[thành viên của cấu trúc]
Trang 2}
Ví dụ 7.1 sau minh họa cách tạo một cấu trúc Kiểu Location thể hiện một điểm trong không gian hai chiều Lưu ý rằng cấu trúc Location này được khai báo chính xác như khi thực hiện khai báo với một lớp, ngoại trừ việc sử dụng từ khóa
struct Ngoài ra cũng lưu ý rằng hàm khởi dựng của Location lấy hai số nguyên và
gán những giá trị của chúng cho các biến thành viên, x và y Tọa độ x và y của Location được khai báo như là thuộc tính
Ví dụ 7.1 Tạo một cấu trúc
-
using System;
public struct Location
{
public Location( int xCoordinate, int yCoordinate)
{
xVal = xCoordinate;
yVal = yCoordinate;
}
public int x
{
get
{
}
set
{
}
}
return xVal;
xVal = value;
public int y
{
get
{
}
set
{
}
}
return yVal;
yVal = value;
Trang 3public override string ToString()
{
return (String.Format(“{0}, {1}”, xVal, yVal));
}
// thuộc tính private lưu toạ độ x, y
private int xVal;
private int yVal;
}
public class Tester
{
public void myFunc( Location loc)
{
loc.x = 50;
loc.y = 100;
Console.WriteLine(“Loc1 location: {0}”, loc);
}
static void Main()
{
Location loc1 = new Location( 200, 300);
Console.WriteLine(“Loc1 location: {0}”,
loc1); Tester t = new Tester();
t.myFunc( loc1 );
Console.WriteLine(“Loc1 location: {0}”, loc1);
}
}
-
Không giống như những lớp, cấu trúc không hỗ trợ việc thừa kế Chúng được thừa
kế ngầm định từ lớp object (tương tự như tất cả các kiểu dữ liệu trong C#, bao gồm các kiểu dữ liệu xây dựng sẵn) nhưng không thể kế thừa từ các lớp khác hay cấu
trúc khác Cấu trúc cũng được ngầm định là sealed, điều này có ý nghĩa là không có
lớp nào hay bất cứ cấu trúc nào có thể dẫn xuất từ nó Tuy nhiên, cũng giống như các lớp, cấu trúc có thể thực thi nhiều giao diện Sau đây là một số sự khác nhau nữa là: Không có bộ hủy và bộ khởi tạo mặc định tùy chọn: Những cấu trúc không có bộ hủy và cũng không có bộ khởi tạo mặc định không tham số tùy chọn Nếu chúng
ta không cung cấp bất cứ bộ khởi tạo nào thì cấu trúc sẽ được cung cấp một bộ khởi tạo mặc định, khi đó giá trị 0 sẽ được thiết lập cho tất cả các dữ liệu thành viên hay những giá trị mặc định tương ứng cho từng kiểu dữ liệu (bảng 4.2) Nếu
Trang 4chúng ta cung cấp bất cứ bộ khởi dựng nào thì chúng ta phải khởi tạo tất cả các
trường trong cấu trúc
Không
cho
phép
khởi
tạo:
chúng
ta
không
thể
khởi
tạo
các
trường
thể
hiện
(instan
ce
fields)
trong
cấu
trúc,
do đó
đoạn
mã
nguồn
sau sẽ
không
hợp
lệ:
p
r
i
v
a
t
e
i
n t
x V a l
=
2 0
; p r i v a t e
i n t
y V a l
=
5 0
; mặc
dù điều này thực hiện tốt đối với lớp
Cấ
Trang 5u trúc
được
thiết kế
hướng
tới đơn
giản và
gọn
nhẹ
Trong
khi các
dữ liệu
thành
viên
private
hỗ trợ
việc
che dấu
dữ liệu
và sự
đóng
gói
Một vài
người
lập
trình có
cảm
giác
rằng
điều
này phá
hỏng
cấu
trúc
Họ tạo
một dữ
liệu
thành
viê
n pub lic,
do vậy đơ
n giả
n thự
c thi mộ
t cấu trú
c Nh ữn
g ng ười lập trìn
h khá
c
có cả
m giá
c rằn
g nh ữn
Trang 6g thuộc
tính
cung
cấp
một
giao
diện rõ
ràng,
đơn
giản và
việc
thực
hiện
lập
trình
tốt đòi
hỏi
phải
che dấu
dữ liệu
thậm
chí với
dữ liệu
rất đơn
giản
Chúng
ta sẽ
chọn
cách
nào, nói
chung
là phụ
thuộc
vào
quan
nệm
thiết kế
của từn
g ng ười lập trìn
h
Dù chọ
n các
h nào thì ngô
n ng
ữ C# cũn
g
hỗ trợ
cả hai các
h tiếp cận
Tạo cấu trúc
C hú
ng
Trang 7ta tạo
một
thể
hiện
của
cấu
trúc
bằng
cách
sử
dụng
từ
khóa
new
trong
câu
lệnh
gán,
như
khi
chúng
ta tạo
một
đối
tượng
của
lớp
Như
trong
ví dụ
7.1,
lớp
Tester
tạo một
thể
hiện
của
Lo cat ion nh
ư sau : L o c a ti o
n l o c
1
= n e w L o c a ti o n ( 2 0 0 , 3 0 0 )
;
Ở đây một thể hiện mới
Trang 8tên là
loc1 và
nó được
truyền
hai giá
trị là 200
và 300
Cấu trúc
là một
kiểu giá
trị
Phần
định
nghĩa
của lớp
Tester
trong
ví dụ
7.1 trên
bao
gồm
một đối
tượng
Locatio
n là
loc1
được
tạo với
giá trị
là 200
và 300
Dòng
lệnh
sau sẽ
gọi
thực
hiện bộ
khởi
tạo của cấu trú
c Lo cat ion : L o c a ti o
n l o c
1
= n e w
L o c a ti o n ( 2 0 0 , 3 0 0 )
; Sau
đó phư
Trang 9ơng tức
WriteLin
e() được
gọi:
Cons
ole.
Write
Line(
“Loc
1
locati
on:
{0}”,
loc1)
;
Dĩ
nhiên
là
WriteLi
ne chờ
đợi một
đối
tượng,
nhưng
Locatio
n là
một
cấu
trúc
(một
kiểu
giá trị)
Trình
biên
dịch sẽ
tự động
boxing
cấu
trúc
(cũng
giố
ng nh
ư trìn
h biê
n dịc
h
đã là
m với các kiể
u
dữ liệ
u giá trị khá c) Mộ
t đối tượ
ng sau khi bo xin
g đư
ợc tru
Trang 10yền vào
cho
phương
thức
WriteLi
ne()
Tiếp
sau đó
là
phương
thức
ToStrin
g()
được
gọi trên
đối
tượng
boxing
này, do
cấu
trúc
ngầm
định kế
thừa từ
lớp
object,
và nó
cũng có
thể đáp
ứng sự
đa
hình,
bằng
cách
phủ
quyết
các
ph ươ
ng thứ
c nh
ư bất
cứ đối tượ
ng nào khá
c L o c
1 l o c a ti o
n 2 0 0 , 3 0
0 Tuy nhiên
do cấu trúc là kiểu giá trị, nên khi
Trang 11truyền vào trong một hàm, thì chúng chỉ truyền giá trị vào hàm Cũng như
ta thấy ở dòng lệnh
kế tiếp, khi đó một đối tượng Location được truyền vào
phương thức myFunc() :
t.my Func ( loc1 );
Trang 12Trong phương thức myFunc() hai giá trị mới được gán cho x và y, sau đó giá trị mới sẽ được xuất ra màn hình:
Loc1 location: 50, 100
Khi phương thức myFunc() trả về cho hàm gọi ( Main()) và chúng ta gọi tiếp phương thức
WriteLine() một lần nữa thì giá trị không thay đổi:
Loc1 location: 200, 300
Như vậy cấu trúc được truyền vào hàm như một đối tượng giá trị, và một bản sao sẽ được tạo bên trong phương thức myFunc() Nếu chúng ta thử đổi khai báo của
Location là class như sau:
public class Location
Sau đó chạy lại chương trình thì có kết quả:
Loc1 location: 200, 3000
In myFunc loc: 50, 100
Loc1 location: 50, 100
Lúc này Location là một đối tượng tham chiếu nên khi truyền vào phương thức
myFunc() thì việc gán giá trị mới cho x và y điều làm thay đổi đối tượng Location
Gọi bộ khởi dựng mặc định
Như đề cập ở phần trước, nếu chúng ta không tạo bộ khởi dựng thì một bộ khởi dựng mặc định ngầm định sẽ được trình biên dịch tạo ra Chúng ta có thể nhìn thấy điều này nếu bỏ bộ khởi dựng tạo ra:
/*public Location( int xCoordinate , int yCoordinate)
{
xVal = xCoordinate;
yVal = yCoordinate;
}
*/
và ta thay dòng lệnh đầu tiên trong hàm Main() tạo Location có hai tham số bằng câu lệnh tạo không tham số như sau:
//Location loc1 = new Location( 200,
300) Location loc1 = new Location();
Bởi vì lúc này không có phương thức khởi dựng nào khai báo, một phương thức khởi dựng
ngầm định sẽ được gọi Kết quả khi thực hiện giống như sau:
Loc1 location 0, 0
In myFunc loc: 50, 100
Loc1 location: 0, 0
Trang 13Bộ khởi tạo mặc định đã thiết lập tất cả các biến thành viên với giá trị 0
Trang 14Ghi chú: Đối với lập trình viên C++ lưu ý, trong ngôn ngữ C#, từ khóa new không
phải luôn luôn tạo đối tượng trên bộ nhớ heap Các lớp thì được tạo ra trên heap, trong
khi các cấu trúc thì được tạo trên stack Ngoài ra, khi new được bỏ qua (sẽ bàn tiếp
trong phần sau), thì bộ khởi dựng sẽ không được gọi Do ngôn ngữ C# yêu cầu phải
có phép gán trước khi sử dụng, chúng ta phải khởi tạo tường minh tất cả các biến thành viên trước khi sử dụng chúng trong cấu trúc