Truyền theo giá trị và tham chiếu
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 21 trên 107.
Go là ngôn ngữ truyền theo giá trị. Khi bạn truyền một biến cho hàm (function), Go tạo một bản sao của giá trị đó. Điều này có nghĩa là những thay đổi bên trong hàm không ảnh hưởng đến biến ban đầu.
func double(n int) {
n = n * 2
}
func main() {
x := 10
double(x)
fmt.Println(x) // 10 (không đổi)
}Điều tương tự cũng áp dụng với struct. Truyền một struct vào hàm (function) sẽ sao chép tất cả các trường của nó, vì vậy những thay đổi bên trong hàm sẽ bị mất.
type Person struct {
Name string
Age int
}
func birthday(p Person) {
p.Age++
}
func main() {
alice := Person{Name: "Alice", Age: 30}
birthday(alice)
fmt.Println(alice.Age) // 30 (không đổi)
}Để thay đổi giá trị ban đầu, hãy truyền vào một con trỏ. Bản thân con trỏ được sao chép, nhưng nó vẫn trỏ đến cùng một địa chỉ bộ nhớ, cho phép bạn thay đổi dữ liệu ban đầu.
func birthday(p *Person) {
p.Age++
}
func main() {
alice := Person{Name: "Alice", Age: 30}
birthday(&alice)
fmt.Println(alice.Age) // 31 (đã thay đổi!)
}Dùng con trỏ khi bạn cần sửa đổi giá trị ban đầu hoặc khi truyền các cấu trúc lớn để tránh sao chép tốn kém. Dùng giá trị khi bạn muốn bảo vệ dữ liệu ban đầu khỏi những thay đổi ngoài ý muốn.
Thử thách
DễHãy xây dựng một hệ thống giỏ hàng để minh họa sự khác biệt giữa việc truyền struct theo giá trị và theo con trỏ. Bạn sẽ thấy tận mắt chỉ tham số con trỏ mới có thể sửa đổi dữ liệu ban đầu.
Bạn sẽ sắp xếp code trong hai tệp:
cart.go: Định nghĩa một structCartItemvới các trườngName(string),Price(float64) vàQuantity(int). Tạo hai hàm để thử cập nhật số lượng:UpdateQuantityByValuenhận mộtCartItem(theo giá trị) và số lượng mới, sau đó đặt số lượng của mặt hàng thành giá trị mớiUpdateQuantityByPointernhận một*CartItem(con trỏ) và số lượng mới, sau đó đặt số lượng của mặt hàng thành giá trị mới
TotalchoCartItem, trả về giá nhân với số lượng.main.go: Đọc thông tin mặt hàng từ đầu vào, tạo một CartItem, sau đó minh họa cả hai cách cập nhật. Trước tiên, thử cập nhật bằng hàm nhận giá trị và hiển thị kết quả, rồi cập nhật bằng hàm nhận con trỏ và hiển thị kết quả lần nữa.
Đầu vào sẽ gồm:
- Dòng 1: Tên mặt hàng
- Dòng 2: Giá (số thực)
- Dòng 3: Số lượng ban đầu (số nguyên)
- Dòng 4: Số lượng mới cần đặt (số nguyên)
Định dạng đầu ra:
Initial: [Name] x[Quantity] = $[Total]
After value update: [Name] x[Quantity] = $[Total]
After pointer update: [Name] x[Quantity] = $[Total]Ví dụ, với đầu vào là Laptop, 999.99, 1 và 3, đầu ra của bạn sẽ là:
Initial: Laptop x1 = $999.99
After value update: Laptop x1 = $999.99
After pointer update: Laptop x3 = $2999.97Hãy chú ý rằng số lượng vẫn là 1 sau khi cập nhật theo giá trị (hàm chỉ sửa đổi một bản sao), nhưng đổi thành 3 sau khi cập nhật theo con trỏ (hàm sửa đổi dữ liệu ban đầu). Định dạng tổng tiền với hai chữ số thập phân.
Tự làm thử
package main
import (
"fmt"
)
func main() {
// Đọc đầu vào
var name string
var price float64
var initialQty, newQty int
fmt.Scanln(&name)
fmt.Scanln(&price)
fmt.Scanln(&initialQty)
fmt.Scanln(&newQty)
// TODO: Tạo CartItem bằng các giá trị đầu vào
// TODO: In ra trạng thái ban đầu theo định dạng:
// fmt.Printf("Initial: %s x%d = $%.2f\n", ...)
// TODO: Thử cập nhật số lượng bằng UpdateQuantityByValue
// Sau đó in ra kết quả (lưu ý số lượng sẽ không thay đổi)
// TODO: Cập nhật số lượng bằng UpdateQuantityByPointer
// Sau đó in ra kết quả (lưu ý số lượng sẽ thay đổi)
}
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