Menu

Struct trong Golang: khai báo, khởi tạo, so sánh và tag

Struct gom các trường có tên vào một kiểu. Tìm hiểu cách khai báo và khởi tạo struct, giá trị zero, con trỏ tới struct, struct ẩn danh, phép so sánh, struct tag và quy ước hàm khởi tạo NewX.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Khai báo và tạo struct

Một kiểu struct là danh sách các trường có tên. Bạn khai báo nó bằng type Name struct { ... } và tạo giá trị bằng composite literal.

Đầu ra:

Ana 31
{Name:Ana Email:ana@example.com Age:32 Admin:false}
{Name: Email: Age:0 Admin:false}
main.User{Name:"Bob", Email:"", Age:0, Admin:false}

%+v in tên trường còn %#v in theo cú pháp Go. Cả hai là cách nhanh nhất để xem một struct khi gỡ lỗi.

Các trường cùng kiểu có thể viết chung một dòng: X, Y float64. Tên trường viết hoa chữ đầu được export (nhìn thấy từ package khác và từ encoding/json); tên viết thường thì không.

Các dạng literal

User{Name: "Ana", Age: 31}        // named fields: order-free, omitted fields are zero
User{"Ana", "a@x.com", 31, false} // positional: every field, in order
&User{Name: "Ana"}                // pointer to a new struct
new(User)                         // pointer to a zeroed struct

Nên dùng tên trường. Literal theo vị trí sẽ hỏng khi có người thêm trường hoặc đổi thứ tự trường, và go vet cảnh báo về chúng với kiểu struct từ package khác (ví dụ net/url.Error struct literal uses unkeyed fields). Dạng theo vị trí vẫn ổn với các kiểu cục bộ rất nhỏ như Point{1, 2}.

Không có giá trị mặc định: giá trị zero và hàm khởi tạo

Go không có giá trị mặc định cho trường và ngôn ngữ cũng không có hàm khởi tạo. Mọi trường bắt đầu ở giá trị zero của kiểu: 0, "", false, nil. Có hai cách viết chuẩn để bù vào.

Làm cho giá trị zero dùng được. sync.Mutex, bytes.Buffer và strings.Builder đều hoạt động mà không cần khởi tạo. Khi có thể thiết kế kiểu theo cách đó, hãy làm vậy.

Viết hàm NewX khi một trường cần giá trị mặc định khác zero hoặc có thứ cần cấp phát, như một map:

NewServer trả về con trỏ vì server có các phương thức thay đổi nó và sẽ được dùng chung. Trả về struct theo giá trị cũng phổ biến với các kiểu nhỏ, bất biến (time.Date trả về một time.Time).

Con trỏ tới struct

Go tự động dereference con trỏ struct khi truy cập trường. Với p := &User{}, bạn viết p.Name, không phải (*p).Name.

Đầu ra:

{11 12}
{11 12} {0 12}
{11 0}

Struct là giá trị. Phép gán và đối số hàm sao chép chúng; con trỏ thì dùng chung một bản. Sao chép từng trường là sao chép nông: trường slice hay map trong bản sao vẫn trỏ tới cùng dữ liệu. Xem trang con trỏ để biết khi nào dùng cách nào.

Struct ẩn danh

Một kiểu struct có thể viết trực tiếp tại chỗ mà không cần tên. Cách này hữu ích cho những nhóm dữ liệu dùng một lần, test dạng bảng và giải mã JSON chỉ đọc một lần.

Bảng []struct{ in string; want int } là dạng chuẩn của một bài test trong Go.

So sánh struct

Struct so sánh được bằng == khi mọi trường đều so sánh được. Bằng nhau nghĩa là tất cả các trường bằng nhau. Struct so sánh được cũng có thể làm khóa của map.

Với struct có trường slice hoặc map, hãy viết phương thức Equal hoặc so sánh trong test bằng reflect.DeepEqual.

Struct tag

Tag là một literal chuỗi đặt sau kiểu của trường. Ngôn ngữ bỏ qua nó; các thư viện đọc nó bằng reflection. Nơi đọc tag phổ biến nhất là encoding/json:

Chương trình in ra {"id":7,"name":"Mug"}: tag đổi tên trường, omitempty bỏ Price có giá trị zero, và trường không export thì bộ mã hóa không nhìn thấy. Tag theo quy ước key:"value", nhiều khóa cách nhau bằng dấu cách: `json:"name" db:"user_name"`. Một tag sai định dạng vẫn biên dịch được và bị bỏ qua mà không báo gì, go vet sẽ phát hiện lỗi này. Trang json giải thích các tùy chọn.

Struct rỗng và thứ tự trường

struct{} không có trường nào và chiếm 0 byte. Nó được dùng làm giá trị của set (map[string]struct{}) và làm tín hiệu trên channel (chan struct{}), nơi chỉ sự kiện là quan trọng.

Thứ tự trường ảnh hưởng tới cách bố trí bộ nhớ: trình biên dịch chèn đệm để các trường thẳng hàng theo căn chỉnh. Một struct gồm bool, int64, bool chiếm 24 byte trên hệ 64 bit, còn int64, bool, bool chiếm 16 byte. Điều này chỉ quan trọng với các kiểu được lưu với số lượng lớn.

Lỗi thường gặp

  • Sửa trên bản sao. for _, u := range users { u.Age++ } mỗi lần chỉ thay đổi một bản sao. Hãy dùng chỉ số: users[i].Age++.
  • Quên khởi tạo trường map. Giá trị zero của struct chứa một map nil; ghi vào nó sẽ panic. Hãy cấp phát trong hàm khởi tạo.
  • Sao chép struct chứa sync.Mutex. Bản sao có trạng thái khóa riêng. Truyền những struct như vậy bằng con trỏ; go vet sẽ báo việc sao chép.
  • Literal theo vị trí cho kiểu của package khác. Chúng sẽ hỏng khi có trường mới được thêm vào.

Câu hỏi thường gặp

Khởi tạo struct trong Go như thế nào?

Bằng composite literal. Cách thường dùng là ghi tên trường: u := User{Name: "Ana", Age: 31}, và trường nào bỏ qua sẽ nhận giá trị zero. User{} cho toàn giá trị zero, &User{...} cho một con trỏ, và new(User) cho con trỏ tới một struct toàn giá trị zero.

Go có hỗ trợ giá trị mặc định cho trường của struct không?

Không. Mọi trường đều bắt đầu ở giá trị zero của kiểu. Cách viết chuẩn là một hàm khởi tạo, func NewServer() *Server { return &Server{Port: 8080} }, hoặc thiết kế kiểu sao cho giá trị zero đã dùng được ngay, như sync.Mutex và bytes.Buffer.

So sánh hai struct trong Go như thế nào?

Bằng ==, nếu mọi trường đều so sánh được. Hai giá trị struct bằng nhau khi tất cả các trường tương ứng bằng nhau. Struct có trường slice, map hoặc hàm thì không so sánh được bằng == (lỗi biên dịch); hãy tự so sánh từng trường hoặc dùng reflect.DeepEqual trong test.

Struct tag trong Go là gì?

Là literal chuỗi đặt sau kiểu của trường, như `json:"name,omitempty"`. Trình biên dịch bỏ qua chúng; các package như encoding/json, driver cơ sở dữ liệu và thư viện kiểm tra dữ liệu đọc chúng bằng reflection để quyết định cách mã hóa, đặt tên hoặc kiểm tra trường.

Làm sao in struct kèm tên trường trong Go?

Dùng fmt.Printf("%+v\n", s), in ra {Name:Ana Age:31}. %v chỉ in giá trị, còn %#v in theo cú pháp Go kèm tên kiểu: main.User{Name:"Ana", Age:31}.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu