Trong hướng dẫn này, chúng ta sẽ tìm hiểu struct là gì, cách khai báo và sử dụng một struct trong Go, struct ẩn danh, so sanh hai struct, ...
Struct là một kiểu do người dùng định nghĩa đại diện cho một tập hợp các trường. Nó có thể được sử dụng ở những nơi có ý nghĩa khi nhóm dữ liệu thành một đơn vị duy nhất thay vì lưu trữ chúng dưới dạng các giá trị riêng biệt.
Ví dụ: một nhân viên có các thuộc tính như họ, tên và tuổi. Sẽ hợp lý khi nhóm ba thuộc tính này thành một cấu trúc duy nhất được đặt tên là Employee
.
type Employee struct {
firstName string
lastName string
age int
}
Đoạn mã trên khai báo một struct kiểu Employee
với các trường firstName
, lastName
và age
. Struct Employee
trên được gọi là struct được đặt tên vì nó tạo ra một kiểu dữ liệu mới được đặt tên là Employee
bằng cách sử dụng struct Employee
có thể được tạo.
Struct này cũng có thể được làm nhỏ gọn hơn bằng cách khai báo các trường thuộc cùng một kiểu trong một dòng duy nhất theo sau là tên kiểu. Trong struct trên firstName
và lastName
thuộc cùng một kiểu dữ liệu string
và do đó struct có thể được viết lại thành
type Employee struct {
firstName, lastName string
age int
}
Mặc dù cú pháp trên tiết kiệm một vài dòng mã, nó không làm cho khai báo trường rõ ràng. Vui lòng không sử dụng cách này để khai báo struct.
Hãy khai báo một struct được đặt tên là Employee bằng chương trình đơn giản sau.
package main
import (
"fmt"
)
type Employee struct {
firstName string
lastName string
age int
salary int
}
func main() {
//creating struct specifying field names
emp1 := Employee{
firstName: "Sam",
age: 25,
salary: 500,
lastName: "Anderson",
}
//creating struct without specifying field names
emp2 := Employee{"Thomas", "Paul", 29, 800}
fmt.Println("Employee 1", emp1)
fmt.Println("Employee 2", emp2)
}
Chạy chương trình trong playground
Ở dòng số 7 của chương trình trên, chúng ta tạo một kiểu struct được đặt tên là Employee
. dòng số 17 của chương trình trên, biến emp1
được định nghĩa bằng cách chỉ định giá trị cho mỗi trường.
Thứ tự của các trường không nhất thiết phải giống thứ tự của tên trường trong khi khai báo kiểu struct. Trong trường hợp này, chúng ta đã thay đổi vị trí của trường lastName
và chuyển nó xuống cuối. Điều này sẽ hoạt động mà không có bất kỳ vấn đề nào.
Ở dòng số 25 của chương trình trên, biến emp2
được định nghĩa bằng cách bỏ qua các tên trường. Trong trường hợp này, cần phải duy trì thứ tự của các trường giống như được chỉ định trong khai báo struct.
Vui lòng không sử dụng cú pháp này vì nó gây khó khăn cho việc tìm ra giá trị nào dành cho trường nào.
Chúng tôi đã chỉ định định dạng này ở đây chỉ để hiểu rằng đây cũng là một cú pháp hợp lệ :)
Chương trình trên in ra kết quả sau:
Employee 1 {Sam Anderson 25 500}
Employee 2 {Thomas Paul 29 800}
Có thể khai báo struct mà không cần tạo kiểu dữ liệu mới. Những kiểu struct này được gọi là struct ẩn danh.
package main
import (
"fmt"
)
func main() {
emp3 := struct {
firstName string
lastName string
age int
salary int
}{
firstName: "Andreah",
lastName: "Nikola",
age: 31,
salary: 5000,
}
fmt.Println("Employee 3", emp3)
}
Chạy chương trình trong playground
Ở dòng số 8 của chương trình trên, một biến struct ẩn danh là emp3
được định nghĩa. Như chúng ta đã đề cập, struct này được gọi là ẩn danh vì nó chỉ tạo một biến struct mới là emp3
và không xác định bất kỳ kiểu struct mới nào như struct được đặt tên.
Chương trình này xuất ra kết quả sau:
Employee 3 {Andreah Nikola 31 5000}
Toán tử dấu chấm .
được sử dụng để truy cập các trường riêng lẻ của một struct.
package main
import (
"fmt"
)
type Employee struct {
firstName string
lastName string
age int
salary int
}
func main() {
emp6 := Employee{
firstName: "Sam",
lastName: "Anderson",
age: 55,
salary: 6000,
}
fmt.Println("First Name:", emp6.firstName)
fmt.Println("Last Name:", emp6.lastName)
fmt.Println("Age:", emp6.age)
fmt.Printf("Salary: $%d\n", emp6.salary)
emp6.salary = 6500
fmt.Printf("New Salary: $%d", emp6.salary)
}
Chạy chương trình trong playground
emp6.firstName
trong chương trình trên truy cập vào trường firstName
của struct emp6
. Ở dòng số 25 chúng ta sửa đổi mức lương của nhân viên. Chương trình này in ra kết quả sau:
First Name: Sam
Last Name: Anderson
Age: 55
Salary: $6000
New Salary: $6500
Khi một struct được định nghĩa và nó không được khởi tạo rõ ràng với bất kỳ giá trị nào, các trường của struct được gán giá trị mặc định theo mặc định.
package main
import (
"fmt"
)
type Employee struct {
firstName string
lastName string
age int
salary int
}
func main() {
var emp4 Employee //zero valued struct
fmt.Println("First Name:", emp4.firstName)
fmt.Println("Last Name:", emp4.lastName)
fmt.Println("Age:", emp4.age)
fmt.Println("Salary:", emp4.salary)
}
Chạy chương trình trong playground
Chương trình trên định nghĩa biến emp4
nhưng nó không được khởi tạo với bất kỳ giá trị nào. Do đó firstName
và lastName
được giao các giá trị mặc định của kiểu string là một chuỗi rỗng ""
và các trường age
, salary
được giao các giá trị mặc định của kiểu int đó là 0. Chương trình này in ra kết quả sau:
First Name:
Last Name:
Age: 0
Salary: 0
Cũng có thể chỉ định giá trị cho một số trường và bỏ qua phần còn lại. Trong trường hợp này, các trường bị bỏ qua được gán giá trị mặc định theo kiểu dữ liệu của trường.
package main
import (
"fmt"
)
type Employee struct {
firstName string
lastName string
age int
salary int
}
func main() {
emp5 := Employee{
firstName: "John",
lastName: "Paul",
}
fmt.Println("First Name:", emp5.firstName)
fmt.Println("Last Name:", emp5.lastName)
fmt.Println("Age:", emp5.age)
fmt.Println("Salary:", emp5.salary)
}
Chạy chương trình trong playground
Trong chương trình trên ở dòng số 16 và 17, trường firstName
và lastName
được khởi tạo, ngược lại trường age
và salary
không được khởi tạo. Do đó age
và salary
được gán giá trị mặc định của kiểu int
là 0. Chương trình này xuất ra:
First Name: John
Last Name: Paul
Age: 0
Salary: 0
Cũng có thể tạo con trỏ trỏ đến một struct.
package main
import (
"fmt"
)
type Employee struct {
firstName string
lastName string
age int
salary int
}
func main() {
emp8 := &Employee{
firstName: "Sam",
lastName: "Anderson",
age: 55,
salary: 6000,
}
fmt.Println("First Name:", (*emp8).firstName)
fmt.Println("Age:", (*emp8).age)
}
Chạy chương trình trong playground
Biến emp8
trong chương trình trên là một con trỏ trỏ đến struct Employee
. (*emp8).firstName
là cú pháp để truy cập trường firstName
của struct emp8
. Chương trình này in ra:
First Name: Sam
Age: 55
Go cung cấp cho chúng ta tùy chọn sử dụng emp8.firstName
thay vì tham chiếu rõ ràng (*emp8).firstName
để truy cập trường firstName
.
package main
import (
"fmt"
)
type Employee struct {
firstName string
lastName string
age int
salary int
}
func main() {
emp8 := &Employee{
firstName: "Sam",
lastName: "Anderson",
age: 55,
salary: 6000,
}
fmt.Println("First Name:", emp8.firstName)
fmt.Println("Age:", emp8.age)
}
Chạy chương trình trong playground
Chúng ta đã sử dụng emp8.firstName
để truy cập trường firstName
trong chương trình trên và chương trình này cũng xuất ra kết quả:
First Name: Sam
Age: 55
Có thể tạo struct với các trường chỉ chứa kiểu mà không có tên trường. Loại trường này được gọi là trường ẩn danh.
Đoạn mã dưới đây tạo một struct là Person
có hai trường ẩn danh string
và int
type Person struct {
string
int
}
Mặc dù các trường ẩn danh không có tên rõ ràng, theo mặc định, tên của trường ẩn danh là tên kiểu dữ liệu của nó.
Ví dụ trong trường hợp struct Person ở trên, mặc dù các trường là ẩn danh, theo mặc định, chúng lấy tên của kiểu dữ liệu của trường. Vì vậy, struct Person
có 2 trường tên là string
và int
.
package main
import (
"fmt"
)
type Person struct {
string
int
}
func main() {
p1 := Person{
string: "naveen",
int: 50,
}
fmt.Println(p1.string)
fmt.Println(p1.int)
}
Chạy chương trình trong playground
Ở dòng số 17 và 18 của chương trình trên, chúng ta truy cập vào các trường ẩn danh của struct Person sử dụng tên của kiểu dữ liệu thay cho tên trường là string
và int
tương ứng. Đầu ra của chương trình trên là:
naveen
50
Struct có thể chứa một trường có kiểu dữ liệu là một struct. Loại struct này được gọi là struct lồng nhau.
package main
import (
"fmt"
)
type Address struct {
city string
state string
}
type Person struct {
name string
age int
address Address
}
func main() {
p := Person{
name: "Naveen",
age: 50,
address: Address{
city: "Chicago",
state: "Illinois",
},
}
fmt.Println("Name:", p.name)
fmt.Println("Age:", p.age)
fmt.Println("City:", p.address.city)
fmt.Println("State:", p.address.state)
}
Chạy chương trình trong playground
Struct Person
trong chương trình trên có một trường address
cũng là một struct. Chương trình này in ra kết quả sau:
Name: Naveen
Age: 50
City: Chicago
State: Illinois
Các trường thuộc về trường struct ẩn danh trong struct được gọi là trường được thăng hạng vì chúng có thể được truy cập như thể chúng thuộc về struct chứa trường struct ẩn danh.
Tôi có thể hiểu rằng định nghĩa này khá phức tạp vì vậy chúng ta hãy đi sâu vào một số đoạn mã để hiểu điều này :).
type Address struct {
city string
state string
}
type Person struct {
name string
age int
Address
}
Trong đoạn mã trên, struct Person
có một trường ẩn danh Address
là một struct. Bây giờ các trường của Address
cụ thể city
và state
được gọi là các trường được thăng hạng vì chúng có thể được truy cập như thể chúng được khai báo trực tiếp trong chính struct Person
.
package main
import (
"fmt"
)
type Address struct {
city string
state string
}
type Person struct {
name string
age int
Address
}
func main() {
p := Person{
name: "Naveen",
age: 50,
Address: Address{
city: "Chicago",
state: "Illinois",
},
}
fmt.Println("Name:", p.name)
fmt.Println("Age:", p.age)
fmt.Println("City:", p.city) //city is promoted field
fmt.Println("State:", p.state) //state is promoted field
}
Chạy chương trình trong playground
Ở dòng số 29 và 30 của chương trình ở trên, các trường được thăng hạng là city
và state
được truy cập như thể chúng được khai báo trong chính struct p
bằng cách sử dụng cú pháp p.city
và p.state
. Chương trình này in ra kết quả sau:
Name: Naveen
Age: 50
City: Chicago
State: Illinois
Nếu một kiểu struct bắt đầu bằng một chữ cái viết hoa, thì đó là kiểu được export và nó có thể được truy cập từ các package khác. Tương tự, nếu các trường của một struct bắt đầu bằng chữ hoa, chúng có thể được truy cập từ các package khác.
Hãy viết một chương trình có các package tùy chỉnh để hiểu rõ hơn điều này.
Tạo một thư mục có tên structs
trong thư mục Documents
của bạn. Hãy thoải mái tạo nó ở bất cứ đâu bạn thích. Tôi thích nó nằm trong thư mục Documents
của mình hơn .
mkdir ~/Documents/structs
Hãy tạo một module Go có tên structs
.
cd ~/Documents/structs/
go mod init structs
Tạo một thư mục computer
khác bên trong structs
.
mkdir computer
Bên trong thư mục computer
, tạo một tệp spec.go
với nội dung sau.
package computer
type Spec struct { //exported struct
Maker string //exported field
Price int //exported field
model string //unexported field
}
Đoạn trên tạo ra một package computer
có chứa một struct được export là Spec
với hai trường được export là Maker
và Price
và một trường không được exported là model
. Hãy import package này từ package main và sử dụng struct Spec
.
Tạo một tập tin có tên main.go
bên trong thư mục structs
và viết chương trình sau vào main.go
package main
import (
"structs/computer"
"fmt"
)
func main() {
spec := computer.Spec {
Maker: "apple",
Price: 50000,
}
fmt.Println("Maker:", spec.Maker)
fmt.Println("Price:", spec.Price)
}
Thư mục structs
nên có cơ cấu như sau,
├── structs
│ ├── computer
│ │ └── spec.go
│ ├── go.mod
│ └── main.go
Ở dòng số 4 của chương trình trên, chúng ta đã import package computer
. Ở dòng số 13 và 14, chúng ta truy cập vào hai trường đã export là Maker
và Price
của struct Spec
.
Chương trình này có thể được chạy bằng cách thực thi lệnh go install
theo sau lệnh structs
. Nếu bạn không chắc chắn về cách chạy chương trình Go, vui lòng truy cập bài viết này để biết thêm.
go install
structs
Chạy các lệnh trên sẽ in ra kết quả sau:
Maker: apple
Price: 50000
Nếu chúng ta cố gắng truy cập vào trường chưa được export model
, trình biên dịch sẽ báo lỗi. Thay thế nội dung của main.go
bằng mã sau:
package main
import (
"structs/computer"
"fmt"
)
func main() {
spec := computer.Spec {
Maker: "apple",
Price: 50000,
model: "Mac Mini",
}
fmt.Println("Maker:", spec.Maker)
fmt.Println("Price:", spec.Price)
}
Ở dòng số 12 của chương trình trên, chúng ta cố gắng truy cập vào trường chưa được export là model
. Chạy chương trình này sẽ dẫn đến lỗi biên dịch.
# structs
./main.go:12:13: unknown field 'model' in struct literal of type computer.Spec
Vì trường model
chưa được export nên không thể truy cập trường từ các package khác.
Struct là các kiểu giá trị và có thể so sánh được nếu từng trường của chúng có thể so sánh được. Hai biến struct được coi là bằng nhau nếu các trường tương ứng của chúng bằng nhau.
package main
import (
"fmt"
)
type name struct {
firstName string
lastName string
}
func main() {
name1 := name{
firstName: "Steve",
lastName: "Jobs",
}
name2 := name{
firstName: "Steve",
lastName: "Jobs",
}
if name1 == name2 {
fmt.Println("name1 and name2 are equal")
} else {
fmt.Println("name1 and name2 are not equal")
}
name3 := name{
firstName: "Steve",
lastName: "Jobs",
}
name4 := name{
firstName: "Steve",
}
if name3 == name4 {
fmt.Println("name3 and name4 are equal")
} else {
fmt.Println("name3 and name4 are not equal")
}
}
Chạy chương trình trong playground
Trong chương trình trên, struct name
chứa hai trường kiểu string
. Vì các chuỗi có thể so sánh được, nên có thể so sánh hai biến kiểu struct name
.
Trong chương trình trên, hai biến name1
và name2
bằng nhau trong khi name3
và name4
thì không. Chương trình này sẽ xuất ra:
name1 and name2 are equal
name3 and name4 are not equal
Các biến kiểu struct không thể so sánh được nếu chúng chứa các trường không thể so sánh được.
package main
import (
"fmt"
)
type image struct {
data map[int]int
}
func main() {
image1 := image{
data: map[int]int{
0: 155,
}}
image2 := image{
data: map[int]int{
0: 155,
}}
if image1 == image2 {
fmt.Println("image1 and image2 are equal")
}
}
Chạy chương trình trong playground
Trong chương trình trên, struct image
chứa một trường data
thuộc kiểu map
. Kiểu map không thể so sánh, do đó image1
và image2
không thể so sánh được. Nếu bạn chạy chương trình này, trình biên dịch sẽ báo lỗi
./prog.go:20:12: invalid operation: image1 == image2 (struct containing map[int]int cannot be compared)
Cảm ơn bạn đã đọc. Vui lòng để lại ý kiến và phản hồi của bạn.
Bạn có thể vui lòng tắt trình chặn quảng cáo ❤️ để hỗ trợ chúng tôi duy trì hoạt động của trang web.
Trong hướng dẫn này, chúng ta sẽ tìm hiểu phương thức (method) trong Go là gì? Cú pháp khai báo phương thức, so sánh phương thức với hàm, ... trong Go.
Trong hướng dẫn này, chúng ta sẽ tìm hiểu cách con trỏ (pointer) hoạt động trong Go và nó khác với con trỏ trong các ngôn ngữ khác như C và C++ như thế nào.
Chuỗi (string) xứng đáng được đề cập đặc biệt trong Go vì chúng khác biệt trong cách triển khai khi so sánh với các ngôn ngữ khác.
Trong hướng dẫn này, chúng ta sẽ tìm hiểu map là gì, cú pháp khai báo map, thêm phần tử, xóa phần tử, duyệt các phần tử, ... của map trong Go.