Con trỏ đến struct
Thuộc phần Lập trình hướng đối tượng trong hành trình GO của Coddy. Bài học 20 trên 107.
Khi làm việc với struct, bạn thường dùng con trỏ để tránh sao chép các cấu trúc dữ liệu lớn và để sửa đổi struct gốc. Con trỏ đến struct lưu địa chỉ bộ nhớ của instance struct đó.
Bạn có thể tạo con trỏ struct bằng toán tử & với một struct có sẵn hoặc dùng trực tiếp toán tử địa chỉ với một struct literal.
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
ptr := &p // con trỏ trỏ tới struct đã có
// Hoặc tạo trực tiếp
ptr2 := &Person{Name: "Bob", Age: 25}Go cung cấp cách viết tắt tiện lợi khi truy cập các trường của struct thông qua con trỏ. Thay vì viết (*ptr).Name, bạn chỉ cần viết ptr.Name. Go tự động giải tham chiếu con trỏ giúp bạn.
ptr := &Person{Name: "Alice", Age: 30}
// Cả hai cách hoạt động giống nhau
fmt.Println((*ptr).Name) // Giải tham chiếu tường minh
fmt.Println(ptr.Name) // Giải tham chiếu tự độngCơ chế tự động giải tham chiếu này cũng áp dụng khi sửa đổi các trường. Những thay đổi thông qua con trỏ sẽ ảnh hưởng đến struct ban đầu.
p := Person{Name: "Alice", Age: 30}
ptr := &p
ptr.Age = 31 // thay đổi giá trị ban đầu
fmt.Println(p.Age) // 31Con trỏ struct rất cần thiết trong các mẫu OOP của Go. Chúng cho phép phương thức sửa đổi trạng thái của receiver, giúp truyền các struct lớn vào hàm một cách hiệu quả và tạo nền tảng để xây dựng các cấu trúc dữ liệu liên kết như cây và danh sách.
Thử thách
DễHãy xây dựng một hệ thống tài khoản ngân hàng để minh họa cách con trỏ đến struct cho phép bạn sửa đổi dữ liệu gốc một cách hiệu quả. Bạn sẽ tạo một tài khoản có thể cập nhật thông qua các thao tác với con trỏ.
Bạn sẽ sắp xếp code trong hai tệp:
account.go: Định nghĩa một structBankAccountvới các trườngOwner(chuỗi),Balance(float64) vàAccountNumber(chuỗi). Tạo một hàm (function) tên làNewAccount, nhận tên chủ tài khoản và số tài khoản làm tham số, rồi trả về một con trỏ đếnBankAccountmới có số dư ban đầu là 0. Tạo thêm hàmDeposit, nhận một con trỏ*BankAccountvà một khoản tiền, sau đó cộng khoản tiền đó vào số dư tài khoản thông qua con trỏ.main.go: Đọc thông tin tài khoản từ đầu vào, tạo tài khoản bằng hàm khởi tạo (constructor) của bạn, hàm này trả về một con trỏ, gửi tiền vào tài khoản bằng con trỏ, rồi hiển thị thông tin tài khoản để xác nhận số dư đã thay đổi.
Đầu vào sẽ gồm:
- Dòng 1: Tên chủ tài khoản
- Dòng 2: Số tài khoản
- Dòng 3: Số tiền gửi (số thực)
Định dạng đầu ra:
Account: [AccountNumber]
Owner: [Owner]
Balance: [Balance]Ví dụ, với đầu vào John Smith, ACC-12345 và 500.50, đầu ra của bạn sẽ là:
Account: ACC-12345
Owner: John Smith
Balance: 500.50Hãy nhớ rằng hàm NewAccount cần trả về *BankAccount bằng toán tử &, còn hàm Deposit có thể sửa đổi số dư trực tiếp thông qua con trỏ bằng cách Go tự động giải tham chiếu, chẳng hạn dùng account.Balance thay cho (*account).Balance.
Tự làm thử
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Đọc tên chủ tài khoản
ownerName, _ := reader.ReadString('\n')
ownerName = strings.TrimSpace(ownerName)
// Đọc số tài khoản
accountNumber, _ := reader.ReadString('\n')
accountNumber = strings.TrimSpace(accountNumber)
// Đọc số tiền gửi
amountStr, _ := reader.ReadString('\n')
amountStr = strings.TrimSpace(amountStr)
amount, _ := strconv.ParseFloat(amountStr, 64)
// TODO: Tạo tài khoản mới bằng hàm (function) NewAccount (trả về con trỏ)
// TODO: Gửi tiền bằng hàm Deposit với con trỏ tài khoản
// TODO: In thông tin tài khoản theo định dạng yêu cầu:
// Tài khoản: [AccountNumber]
// Chủ tài khoản: [Owner]
// Số dư: [Balance]
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Nền tảng OOP trong Go
Tệp bên ngoàiWorkspace và module trong GoPackage và importTên được export và không được exportGiới thiệu về OOP trong GoStruct như lớpĐịnh nghĩa phương thức cho structReceiver kiểu con trỏ và kiểu giá trịKhởi tạo structHàm khởi tạoÔn tập: máy tính đơn giản4Interface
Giới thiệu về interfaceTriển khai ngầm địnhInterface như một hợp đồngInterface rỗng (any)Khẳng định kiểuswitch kiểuKết hợp interfaceInterface Stringer và ErrorÔn tập: Máy tính hình học7Tính đóng gói
Trường được xuất và không được xuấtĐóng gói ở cấp packagePhương thức getter và setterẨn thông tin trong GoÔn tập: hồ sơ học sinh10Generics (Go 1.18 trở lên)
Giới thiệu về GenericsTham số kiểuRàng buộc kiểuStruct tổng quátGiải pháp thay thế cho phương thức tổng quátÔn tập: tập hợp tổng quát13Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếMẫu SingletonMẫu FactoryMẫu Abstract FactoryMẫu ObserverMẫu Strategy2Tìm hiểu chuyên sâu về kiểu dữ liệu và struct
Kiểu cơ bản và kiểu tổng hợpĐịnh nghĩa kiểu dữ liệu tùy chỉnhThẻ structStruct ẩn danhStruct lồng nhauGiá trị zero và giá trị mặc địnhÔn tập: sổ liên lạc5Ưu tiên kết hợp hơn kế thừa
Vì sao Go không có kế thừaNhập môn nhúng structTự động nâng phương thứcNhúng nhiều structNhúng và kết tậpChe khuất phương thức được nhúngÔn tập: hệ phân cấp nhân viên8Xử lý lỗi và lập trình hướng đối tượng
Giao diện errorCác kiểu lỗi tùy chỉnhBọc lỗi với fmt.ErrorfLỗi sentinelerrors.Is() và errors.As()Panic, defer và recoverÔn tập: trình phân tích tệp3Con trỏ và bộ nhớ
Cơ bản về con trỏ trong GoCon trỏ đến structTruyền theo giá trị và tham chiếuHàm new()Thu gom rác trong GoÔn tập: xây dựng danh sách liên kết6Đa hình trong Go
Đa hình qua interfaceĐịnh kiểu vịt trong GoQuy tắc thỏa mãn interfaceCác tập hợp đa hìnhTiêm phụ thuộcÔn tập: bộ xử lý thanh toán9Lập trình đồng thời và lập trình hướng đối tượng
Cơ bản về goroutineChannel và giao tiếpChannel có bộ đệm và không có bộ đệmCâu lệnh selectsync.Mutex và sync.RWMutexsync.WaitGroupThiết kế struct an toàn luồngÔn tập: worker poolTự luyện tập: Trình biên dịch Go online