Variables
1. Tổng quan về Variables trong Go
Biến trong Go là một cách để lưu trữ dữ liệu trong bộ nhớ, được đặt tên và có kiểu dữ liệu cụ thể. Go là một ngôn ngữ statically typed (kiểu tĩnh), nghĩa là kiểu của biến phải được xác định tại thời điểm biên dịch và không thể thay đổi trong thời gian chạy.
Các đặc điểm chính của biến trong Go:
Khai báo rõ ràng: Bạn phải khai báo biến với kiểu hoặc để Go suy ra kiểu (type inference).
Zero value: Nếu biến không được gán giá trị ban đầu, Go tự động gán giá trị mặc định (zero value) dựa trên kiểu.
Scope: Biến có phạm vi rõ ràng (package-level, function-level, block-level).
Immutability: Go không có từ khóa
const
cho biến thông thường, nhưng bạn có thể dùngconst
cho hằng số.
2. Cách khai báo biến trong Go
Go cung cấp nhiều cách để khai báo biến, và mỗi cách có ngữ cảnh sử dụng riêng.
2.1. Sử dụng từ khóa var
Cú pháp:
var variableName type
var variableName type = initialValue
Ví dụ:
var x int // Khai báo biến x kiểu int, giá trị mặc định là 0
var y int = 42 // Khai báo và gán giá trị ban đầu
var name string = "Golang" // Biến kiểu string
Zero value:
int
,float64
, ...:0
string
:""
(chuỗi rỗng)bool
:false
pointer
,slice
,map
,channel
,interface
:nil
2.2. Khai báo ngắn gọn với :=
(Short Variable Declaration)
Cú pháp:
variableName := initialValue
Ví dụ:
x := 42 // Go tự suy ra kiểu là int
name := "Golang" // Go tự suy ra kiểu là string
Lưu ý:
Chỉ sử dụng
:=
trong các block scope (ví dụ: trong hàm).Không sử dụng
:=
ở package-level.:=
yêu cầu gán giá trị ban đầu.Nếu biến đã tồn tại,
:=
sẽ báo lỗi. Thay vào đó, dùng=
để gán lại giá trị.
2.3. Khai báo nhiều biến cùng lúc
Go cho phép khai báo nhiều biến trong một dòng, rất tiện lợi.
Ví dụ:
var (
x int = 10
y string = "hello"
z bool = true
)
Hoặc với short declaration:
a, b, c := 1, "two", true
2.4. Khai báo ở Package-level
Biến ở cấp package (ngoài hàm) phải dùng var
, không thể dùng :=
.
Ví dụ:
package main
import "fmt"
var globalVar string = "I'm global"
func main() {
fmt.Println(globalVar)
}
3. Kiểu dữ liệu và Type Inference
Go hỗ trợ nhiều kiểu dữ liệu cơ bản (int
, string
, bool
, float64
, v.v.) và kiểu phức hợp (slice
, map
, struct
, v.v.). Khi khai báo biến, bạn có thể chỉ định kiểu rõ ràng hoặc để Go suy ra kiểu.
Ví dụ về type inference:
x := 42 // int
y := 3.14 // float64
z := "hello" // string
w := true // bool
Chuyên sâu:
Type inference không phải là "đoán mò". Go xác định kiểu dựa trên giá trị ban đầu tại thời điểm biên dịch.
Nếu bạn cần kiểm soát kiểu chính xác (ví dụ:
int32
thay vìint
), hãy khai báo kiểu rõ ràng:var x int32 = 42
4. Scope và Lifetime của biến
4.1. Scope
Package-level: Biến khai báo ngoài hàm có phạm vi toàn package. Có thể được truy cập từ bất kỳ hàm nào trong package (hoặc từ package khác nếu được export - tên bắt đầu bằng chữ in hoa).
Function-level: Biến khai báo trong hàm chỉ tồn tại trong hàm đó.
Block-level: Biến khai báo trong một block (ví dụ:
if
,for
) chỉ tồn tại trong block đó.
Ví dụ:
package main
import "fmt"
var global = "I'm global"
func main() {
local := "I'm local"
fmt.Println(global, local)
if true {
blockVar := "I'm in a block"
fmt.Println(blockVar)
}
// fmt.Println(blockVar) // Lỗi: blockVar không tồn tại ngoài block
}
4.2. Lifetime
Biến tồn tại trong suốt thời gian scope của nó còn hoạt động.
Biến ở package-level tồn tại suốt vòng đời của chương trình.
Biến trong hàm/block được garbage collector dọn dẹp khi hàm/block kết thúc.
Chuyên sâu:
Go không cho phép shadowing biến trong cùng block nếu không cần thiết. Ví dụ:
x := 10 x := 20 // Lỗi: no new variables on left side of :=
Tuy nhiên, shadowing được phép trong các block con:
x := 10 if true { x := 20 // Shadowing, x trong block này là biến mới fmt.Println(x) // In: 20 } fmt.Println(x) // In: 10
5. Hằng số (Constants)
Hằng số trong Go không phải là biến thông thường, nhưng liên quan mật thiết. Hằng số được khai báo bằng const
và có giá trị không thể thay đổi.
Ví dụ:
const Pi float64 = 3.14159
const MaxUsers = 1000 // Untyped constant
Chuyên sâu:
Hằng số có thể là untyped (không có kiểu cụ thể) cho đến khi được sử dụng:
const X = 42 // Untyped var y int = X // X tự động trở thành int
Hằng số chỉ hỗ trợ các kiểu cơ bản (
int
,float
,string
,bool
).Go hỗ trợ constant expressions được tính toán tại thời điểm biên dịch:
const A = 10 const B = A * 2 // B = 20
6. Best Practices khi làm việc với biến
Một Senior Golang cần hiểu và áp dụng các best practices để viết code sạch và hiệu quả:
Ưu tiên khai báo ngắn gọn (
:=
) trong hàm:Giảm boilerplate, tăng tính đọc.
Ví dụ:
x := 42
thay vìvar x int = 42
.
Khai báo kiểu rõ ràng khi cần thiết:
Khi bạn muốn đảm bảo kiểu chính xác (ví dụ:
int32
thay vìint
).Ví dụ:
var x int32 = 42
.
Tránh shadowing không cần thiết:
Shadowing có thể gây nhầm lẫn. Hãy kiểm tra kỹ các biến trong block con.
Sử dụng zero value hợp lý:
Tận dụng zero value thay vì gán giá trị mặc định không cần thiết.
Ví dụ: Không cần
var x int = 0
, vìvar x int
đã là0
.
Đặt tên biến rõ ràng:
Tên biến nên ngắn gọn nhưng mô tả ý nghĩa. Ví dụ:
userCount
thay vìuc
.Biến package-level nên có tên exported (bắt đầu bằng chữ in hoa) nếu cần truy cập từ package khác.
Hạn chế biến toàn cục (global variables):
Biến package-level có thể gây khó kiểm soát trạng thái. Thay vào đó, truyền biến qua tham số hàm.
Kiểm tra lỗi khi gán nhiều giá trị:
Khi dùng
:=
với nhiều giá trị, đảm bảo xử lý lỗi đúng cách:f, err := os.Open("file.txt") if err != nil { log.Fatal(err) }
7. Các chi tiết nâng cao
7.1. Memory Allocation
Biến trong Go được cấp phát trên stack hoặc heap tùy thuộc vào cách sử dụng.
Biến cục bộ (local) thường ở stack, nhưng nếu chúng "escape" (ví dụ: được tham chiếu bởi con trỏ hoặc trả về từ hàm), chúng sẽ được cấp phát trên heap.
Công cụ
go build -gcflags '-m'
có thể dùng để phân tích escape analysis:func main() { x := 42 p := &x // x escapes to heap fmt.Println(*p) }
7.2. Type Conversion
Go không tự động chuyển đổi kiểu, bạn phải ép kiểu rõ ràng:
var x int = 42 var y float64 = float64(x)
7.3. Unused Variables
Go không cho phép biến khai báo mà không sử dụng (lỗi biên dịch). Để tạm thời bỏ qua, dùng
_
:_ := 42 // Biến tạm thời không dùng
7.4. Concurrency và Variables
Khi làm việc với goroutines, biến cần được quản lý cẩn thận để tránh data race.
Dùng
sync.Mutex
hoặc channel để đồng bộ:var counter int var mu sync.Mutex func increment() { mu.Lock() counter++ mu.Unlock() }
8. Ví dụ thực tế
Dưới đây là một ví dụ kết hợp các khái niệm về biến trong một chương trình thực tế:
package main
import (
"fmt"
"sync"
)
var globalCounter int // Package-level variable
const MaxRetries = 3 // Constant
func main() {
// Short declaration
name := "Golang Developer"
retries := 0
// Explicit declaration
var wg sync.WaitGroup
// Function-level variable
process := func(id int) {
defer wg.Done()
for retries < MaxRetries {
mu.Lock()
globalCounter++
fmt.Printf("Worker %d incremented counter to %d\n", id, globalCounter)
mu.Unlock()
retries++
}
}
// Launch goroutines
wg.Add(2)
go process(1)
go process(2)
wg.Wait()
fmt.Printf("Final counter: %d, Name: %s\n", globalCounter, name)
}
9. Kết luận
Hiểu sâu về biến trong Go không chỉ là nắm cú pháp mà còn là biết cách sử dụng chúng hiệu quả trong các ngữ cảnh thực tế, từ quản lý bộ nhớ, tối ưu hiệu suất, đến xử lý concurrency. Một Senior Golang sẽ luôn cân nhắc scope, type safety, và best practices để viết code rõ ràng, dễ bảo trì, và ít lỗi.
Last updated