Skip to content

Biến trong Go

Biến là vị trí lưu trữ dùng để lưu một giá trị, cho phép giá trị lưu trữ của nó thay đổi động trong thời gian chạy. Mỗi khi khai báo một biến, sẽ phân bổ một vùng bộ nhớ để lưu trữ giá trị của kiểu tương ứng, đến Sổ tay tham khảo - Biến để xem thêm chi tiết.

Khai báo

Trong Go việc khai báo kiểu là hậu trí, việc khai báo biến sẽ dùng đến từ khóa var, định dạng là var tên_biến tên_kiểu, quy tắc đặt tên biến phải tuân theo quy tắc đặt tên định danh.

go
var intNum int
var str string
var char byte

Khi muốn khai báo nhiều biến cùng kiểu, có thể chỉ viết kiểu một lần

go
var numA, numB, numC int

Khi muốn khai báo nhiều biến khác kiểu, có thể sử dụng () để bao bọc, có thể tồn tại nhiều ().

go
var (
  name    string
  age     int
  address string
)

var (
  school string
  class int
)

Một biến nếu chỉ khai báo mà không gán giá trị, thì giá trị lưu trữ của biến là giá trị 0 của kiểu tương ứng.

Gán giá trị

Việc gán giá trị sẽ dùng đến toán tử =, ví dụ

go
var name string
name = "jack"

Cũng có thể gán giá trị trực tiếp khi khai báo

go
var name string = "jack"

Hoặc như vậy cũng được

go
var name string
var age int
name, age = "jack", 1

Cách thứ hai mỗi lần đều phải chỉ định kiểu, có thể sử dụng đường cú pháp do chính thức cung cấp: khởi tạo biến ngắn, có thể bỏ qua từ khóa var và kiểu hậu trí, cụ thể là kiểu gì giao cho trình biên dịch tự suy luận.

go
name := "jack" // Biến kiểu chuỗi.

Tuy có thể không cần chỉ định kiểu, nhưng khi gán giá trị sau này, kiểu phải giữ nhất quán, đoạn mã như dưới đây không thể biên dịch.

a := 1
a = "1"

Còn cần lưu ý là, khởi tạo biến ngắn không thể sử dụng nil, vì nil không thuộc bất kỳ kiểu nào, trình biên dịch không thể suy luận kiểu của nó.

go
name := nil // Không thể biên dịch

Khai báo biến ngắn có thể khởi tạo hàng loạt

go
name, age := "jack", 1

Cách khai báo biến ngắn không thể sử dụng cho một biến đã tồn tại, ví dụ

go
// Ví dụ sai
var a int
a := 1

// Ví dụ sai
a := 1
a := 2

Nhưng có một trường hợp ngoại lệ, đó là khi gán giá trị biến cũ đồng thời khai báo một biến mới, ví dụ

go
a := 1
a, b := 2, 2

Đoạn mã như vậy có thể biên dịch, biến a được gán giá trị lại, còn b là khai báo mới.

Trong ngôn ngữ Go, có một quy tắc, đó là tất cả các biến trong hàm đều phải được sử dụng, ví dụ đoạn mã dưới đây chỉ khai báo biến, nhưng không sử dụng nó

go
func main() {
  a := 1
}

Thì khi biên dịch sẽ báo lỗi, nhắc nhở bạn biến này đã khai báo nhưng không sử dụng

a declared and not used

Quy tắc này chỉ áp dụng cho biến trong hàm, đối với biến cấp gói bên ngoài hàm thì không có hạn chế này, đoạn mã dưới đây có thể biên dịch.

go
var a = 1

func main() {

}

Ẩn danh

Dùng dấu gạch dưới có thể biểu thị không cần một biến nào đó

go
Open(name string) (*File, error)

Ví dụ hàm os.Open có hai giá trị trả về, chúng ta chỉ muốn lấy cái đầu tiên, không muốn cái thứ hai, có thể viết như sau

go
file, _ := os.Open("readme.txt")

Biến không được sử dụng là không thể biên dịch, khi bạn không cần một biến nào đó, có thể sử dụng dấu gạch dưới _ để thay thế.

Hoán đổi

Trong Go, nếu muốn hoán đổi giá trị của hai biến, không cần sử dụng con trỏ, có thể sử dụng toán tử gán để trực tiếp hoán đổi, về cú pháp trông rất trực quan, ví dụ như sau

go
num1, num2 := 25, 36
num1, num2 = num2, num1

Ba biến cũng như vậy

go
num1, num2, num3 := 25, 36, 49
num1, num2, num3  = num3, num2, num1

Suy nghĩ đoạn mã dưới đây, đây là một đoạn mã nhỏ tính dãy số Fibonacci, giá trị của ba biến sau khi tính toán lần lượt là gì

go
a, b, c := 0, 1, 1
a, b, c = b, c, a+b

Đáp án là

1 1 1

Bạn có thể nghi ngờ tại sao không phải là

1 1 2

Rõ ràng a đã được gán giá trị của b, tại sao kết quả của a+b vẫn là 1? Go khi thực hiện phép toán gán nhiều biến, thứ tự của nó là trước tính giá trị rồi gán, không phải tính từ trái sang phải.

go
a, b, c = b, c, a+b

Bạn có thể cho rằng nó sẽ được triển khai thành đoạn dưới đây

go
a = b
b = c
c = a + b

Nhưng thực tế nó sẽ tính toán giá trị của ba số a, b, c rồi gán cho chúng, cũng giống như đoạn mã dưới đây

go
a, b, c = 1, 1, 0+1

Khi liên quan đến gọi hàm, hiệu quả này càng rõ ràng hơn, chúng ta có một hàm sum có thể tính giá trị trả về của hai số

go
func sum(a, b int) int {
  return a + b
}

Thông qua hàm để thực hiện hai số cộng nhau

go
a, b, c := 0, 1, 1
a, b, c = b, c, sum(a, b)

Kết quả không thay đổi, khi tính giá trị trả về của hàm sum, tham số đầu vào của nó vẫn là 0 và 1

1 1 1

Nên đoạn mã nên viết tách ra như vậy.

go
a, b = b, c
c = a + b

So sánh

Việc so sánh giữa các biến có một tiền đề lớn, đó là kiểu giữa chúng phải giống nhau, trong ngôn ngữ Go không tồn tại chuyển đổi kiểu ngầm, đoạn mã như dưới đây không thể biên dịch

go
func main() {
  var a uint64
  var b int64
  fmt.Println(a == b)
}

Trình biên dịch sẽ nói với bạn kiểu giữa hai cái không giống nhau

invalid operation: a == b (mismatched types uint64 and int64)

Nên phải sử dụng chuyển đổi kiểu bắt buộc

go
func main() {
  var a uint64
  var b int64
  fmt.Println(int64(a) == b)
}

Trước khi có泛型, hàm tích hợp min, max do Go cung cấp ban đầu chỉ hỗ trợ số dấu phẩy động, đến phiên bản 1.21, Go cuối cùng đã viết lại hai hàm tích hợp này bằng泛型, hiện tại có thể sử dụng hàm min để so sánh giá trị nhỏ nhất

go
minVal := min(1, 2, -1, 1.2)

Sử dụng hàm max để so sánh giá trị lớn nhất

go
maxVal := max(100, 22, -1, 1.12)

Tham số của chúng hỗ trợ tất cả các kiểu có thể so sánh, kiểu có thể so sánh trong Go có

  • Boolean
  • Số
  • Chuỗi
  • Con trỏ
  • Kênh (chỉ hỗ trợ phán đoán có bằng nhau hay không)
  • Mảng có kiểu phần tử là kiểu có thể so sánh (slice không thể so sánh) (chỉ hỗ trợ phán đoán có bằng nhau hay không) (chỉ hỗ trợ so sánh giữa các mảng cùng độ dài, vì độ dài mảng cũng là một phần của kiểu, và kiểu khác nhau không thể so sánh)
  • Struct có kiểu trường đều là kiểu có thể so sánh (chỉ hỗ trợ phán đoán có bằng nhau hay không)

Ngoài ra, cũng có thể thông qua nhập thư viện chuẩn cmp để phán đoán, nhưng chỉ hỗ trợ tham số kiểu có thứ tự, trong Go kiểu có thứ tự tích hợp chỉ có số và chuỗi.

go
import "cmp"

func main() {
  cmp.Compare(1, 2)
  cmp.Less(1, 2)
}

Khối mã

Bên trong hàm, có thể thông qua dấu ngoặc nhọn để xây dựng một khối mã, phạm vi biến giữa các khối mã độc lập với nhau. Ví dụ đoạn mã dưới đây

go
func main() {
  a := 1

  {
    a := 2
    fmt.Println(a)
  }

  {
    a := 3
    fmt.Println(a)
  }
  fmt.Println(a)
}

Kết quả xuất của nó là

2
3
1

Biến giữa các khối độc lập với nhau, không bị ảnh hưởng, không thể truy cập, nhưng sẽ bị ảnh hưởng bởi khối cha.

go
func main() {
  a := 1

  {
    a := 2
    fmt.Println(a)
  }

  {
    fmt.Println(a)
  }
  fmt.Println(a)
}

Kết quả xuất của nó là

2
1
1

Golang by www.golangdev.cn edit