Struct trong Go: Các khái niệm và cách sử dụng

1. Giới thiệu về struct

Trong Go, kiểu dữ liệu struct tương tự như class trong Java, dùng để nhóm các trường dữ liệu có liên quan lại với nhau. Cú pháp khai báo cơ bản như sau:

type TenStruct struct {
    truong1 kieu1
    truong2 kieu2
    ...
}

Quy tắc về tầm nhìn (visibility) được áp dụng cho tên struct và tên trường: nếu bắt đầu bằng chữ cái in hoa thì có thể truy cập từ bên ngoài package. Tên đầy đủ của struct là tên_gói.tên_struct.

2. Các cách khởi tạo struct

Trong Go, new(TenStruct)&TenStruct{} tương đương nhau: cả hai đều cấp phát bộ nhớ và trả về con trỏ tới struct, các trường được khởi tạo với giá trị zero tương ứng.

Khi khởi tạo theo thứ tự (không chỉ rõ tên trường), bạn buộc phải cung cấp giá trị cho tất cả các trường và theo đúng thứ tự khai báo.

type Nguoi struct {
    ma   int
    ten  string
    tuoi int
}

// Cách 1: new
p1 := new(Nguoi)
fmt.Printf("p1: %v\n", p1) // &{0  0}
p1.ma = 1
p1.ten = "Nguyen"
p1.tuoi = 30
fmt.Printf("p1: %v\n", p1) // &{1 Nguyen 30}

// Cách 2: &TenStruct{} (theo thứ tự)
p2 := &Nguoi{2, "Tran", 25}
fmt.Printf("p2: %v\n", p2) // &{2 Tran 25}

// Cách 3: khai báo biến và gán giá trị zero
var p3 Nguoi
fmt.Printf("p3: %v\n", p3) // {0  0}
p3 = Nguoi{3, "Le", 22}
fmt.Printf("p3: %v\n", p3) // {3 Le 22}

// Cách 4: initializer (theo thứ tự)
p4 := Nguoi{4, "Pham", 28}
fmt.Printf("p4: %v\n", p4) // {4 Pham 28}

Khi chỉ rõ tên trường, thứ tự các trường có thể khác nhau và bạn có thể bỏ qua một số trường (các trường bị bỏ qua sẽ nhận giá trị zero).

p5 := Nguoi{ma: 5, ten: "Hoang"} // tuoi tự động là 0
fmt.Printf("p5: %v\n", p5) // {5 Hoang 0}

3. Struct có tag (thẻ)

Bạn có thể gắn thẻ (tag) cho mỗi trường trong struct. Thẻ là một chuỗi ký tự được đặt ngay sau kiểu dữ liệu. Dùng gói reflect để lấy thẻ đó.

type SinhVien struct {
    maSV   int    "mã sinh viên"
    hoTen string "họ và tên"
}

sv := SinhVien{100, "Minh Anh"}
t := reflect.TypeOf(sv)
truong1 := t.Field(0)
fmt.Printf("Trường 1: tên=%v, tag=%v\n", truong1.Name, truong1.Tag) // maSV, mã sinh viên
truong2 := t.Field(1)
fmt.Printf("Trường 2: tên=%v, tag=%v\n", truong2.Name, truong2.Tag) // hoTen, họ và tên

4. Trường ẩn danh (Anonymous field)

Trong struct, bạn có thể khai báo một trường chỉ với kiểu dữ liệu mà không có tên. Tên của trường đó mặc định sẽ là tên của kiểu. Vì vậy, mỗi struct chỉ được phép có tối đa một trường ẩn danh với cùng một kiểu, nhưng có thể có nhiều trường ẩn danh khác kiểu.

type Diem struct {
    toan int
    van  int
}

type HocSinh struct {
    Diem               // trường ẩn danh
    maSo int
}

hs := HocSinh{Diem{8, 7}, 101}
fmt.Println(hs.maSo)    // 101
fmt.Println(hs.toan)    // 8 (truy cập trực tiếp vào trường của Diem)
fmt.Println(hs.van)     // 7

5. Nhúng struct (Embedded struct)

Khi một struct được nhúng vào struct khác (kiểu trường ẩn danh), các trường và phương thức của struct con sẽ được nâng lên struct cha. Nếu có xung đột tên, trường của struct cha có độ ưu tiên cao hơn.

type A struct {
    id   int
    name string
}

type B struct {
    A              // nhúng struct A
    age  int
    name string    // xung đột với A.name
}

b1 := B{A{1, "DefaultA"}, 25, "DefaultB"}
fmt.Printf("%+v\n", b1) // {A:{id:1 name:DefaultA} age:25 name:DefaultB}

// Truy cập trường không xung đột: trực tiếp
fmt.Println(b1.id) // 1

// Trường xung đột: ưu tiên trường của B
b1.name = "UpdatedB"
fmt.Printf("%+v\n", b1) // {A:{id:1 name:DefaultA} age:25 name:UpdatedB}

// Truy cập trường xung đột của A: dùng cú pháp b1.A.name
b1.A.name = "UpdatedA"
fmt.Printf("%+v\n", b1) // {A:{id:1 name:UpdatedA} age:25 name:UpdatedB}

Thẻ: golang struct Go embedded struct anonymous field

Đăng vào ngày 12 tháng 9 lúc 12:02