Menu

Hằng số trong Golang: const có kiểu và không có kiểu

Cách const hoạt động trong Go: khai báo hằng số, khác biệt giữa hằng số có kiểu và không có kiểu, biểu thức hằng với độ chính xác tùy ý, và lý do Go không có slice, map hay struct hằng.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Hằng số (constant) là một giá trị được cố định lúc biên dịch. Khai báo nó bằng const, và Go từ chối mọi nỗ lực thay đổi nó.

Hằng số có thể khai báo ở cấp package hoặc bên trong hàm, mỗi dòng một hằng hoặc gộp trong một khối const ( ... ). MB và GB được tính từ các hằng số khác, và trình biên dịch thực hiện phép tính đó một lần, lúc build.

Gán giá trị cho một hằng số, như MaxRetries = 5, là lỗi biên dịch:

./main.go:8:2: cannot assign to MaxRetries (neither addressable nor a map index expression)

Những gì có thể là hằng số

Chỉ ba loại giá trị: boolean, số (số nguyên, số thực, số phức và rune) và chuỗi. Giá trị phải do trình biên dịch tính được, tức là literal, các hằng số khác, phép tính trên chúng, và vài hàm có sẵn như len của một chuỗi hằng.

Bất cứ thứ gì cần chạy code hoặc cấp phát bộ nhớ đều bị loại:

const Colors = []string{"red", "green"} // slice
const Started = time.Now()              // function call
./main.go:9:16: []string{…} (value of type []string) is not constant
./main.go:10:13: time.Now() (value of struct type time.Time) is not constant

Vì sao Go không có slice hay map hằng

Slice, map và các struct chứa chúng đều tham chiếu tới vùng nhớ chỉ tồn tại lúc chạy, nên trình biên dịch không thể đóng chúng vào chương trình như giá trị cố định, và Go không có từ khóa readonly hay final cho biến. Các cách thay thế phổ biến:

Bản sao tốn một lần cấp phát cho mỗi lần gọi, chấp nhận được với dữ liệu cỡ cấu hình. Để tra cứu theo khóa, một switch trong hàm là một lựa chọn khác gần giống hằng: func statusText(code int) string { switch code { ... } }.

Hằng số không có kiểu

Một hằng số khai báo không kèm kiểu là hằng không có kiểu (untyped). Nó có một loại (số nguyên, số thực, rune, chuỗi, bool) nhưng chưa có kiểu Go cụ thể nào cho đến khi được dùng, và nó thích ứng theo ngữ cảnh:

Ratio dùng được như int, float64, uint8 và float32 mà không cần chuyển kiểu. Đó là lý do time.Sleep(2 * time.Second) biên dịch được: 2 không có kiểu và trở thành time.Duration.

Khi ngữ cảnh không đòi hỏi một kiểu, như với x := Ratio, hằng số nhận kiểu mặc định của nó:

Loại hằng số không có kiểuKiểu mặc định
số nguyên (42)int
số thực (4.2)float64
rune ('a')rune (int32)
số phức (2i)complex128
chuỗistring
booleanbool

Phép tính chính xác

Hằng số không có kiểu dạng số là chính xác tuyệt đối. Trình biên dịch biểu diễn số nguyên bằng ít nhất 256 bit, nên các giá trị trung gian có thể lớn hơn rất nhiều so với mọi kiểu trong Go:

Bản thân Huge không in ra được dưới dạng số nguyên, vì truyền nó vào fmt.Println sẽ chuyển nó thành int, và trình biên dịch bắt được điều đó (float64(Huge) thì được, vì số thực chứa nổi độ lớn này):

cannot use Huge (untyped int constant 1267650600228229401496703205376) as int value in argument to fmt.Println (overflows)

Cùng phép kiểm tra đó chặn những lỗi nhỏ hơn ngay lúc biên dịch, như var b byte = 300:

cannot use 300 (untyped int constant) as byte value in variable declaration (overflows)

Hằng số có kiểu

Gán kiểu cho một hằng số và nó sẽ cư xử như một giá trị của kiểu đó ở mọi nơi, kể cả việc Go từ chối trộn lẫn các kiểu:

const Limit int = 10

var f float64 = 2
fmt.Println(Limit * f)
invalid operation: Limit * f (mismatched types int and float64)

Với const Limit = 10 (không có kiểu), Limit * f biên dịch được và cho kết quả 20. Vì vậy hãy để hằng số không có kiểu, trừ khi kiểu mang ý nghĩa. Kiểu có ý nghĩa khi bạn khai báo hằng số thuộc một kiểu có tên do bạn tự định nghĩa, đó là cách Go tạo enum:

type Weekday int

const (
	Sunday Weekday = iota
	Monday
	Tuesday
)

Ở đây kiểu chính là mục đích: một hàm nhận Weekday ghi rõ nó cần gì. iota và các mẫu enum dựa trên nó được trình bày ở trang enum và iota.

const so với var

constvar
Có thể thay đổikhôngcó
Giá trị được biếtlúc biên dịchlúc chạy
Kiểu được phépbool, số, chuỗimọi kiểu
Chiếm bộ nhớ lúc chạykhôngcó
Lấy được địa chỉ (&x)khôngcó
Không dùng đến là lỗikhôngcó, với biến cục bộ

Hãy dùng const cho mọi thứ thực sự cố định: giới hạn, kích thước, tên, mã giao thức, chuỗi định dạng. Bạn được kiểm tra tràn số lúc biên dịch và phép tính chính xác mà không tốn gì.

Những điểm dễ vấp

Hằng số có kiểu không thích ứng. const Timeout int = 5 không truyền được vào chỗ cần time.Duration nếu không chuyển kiểu; còn const Timeout = 5 không có kiểu thì nhân được: Timeout * time.Second.

Phép chia số nguyên xảy ra trên hằng số nguyên. const Half = 1 / 2 bằng 0, vì cả hai toán hạng đều là số nguyên không có kiểu. Viết 1.0 / 2 để được 0.5.

Không có địa chỉ. &MaxRetries không biên dịch được. Nếu một API cần *int, hãy sao chép hằng số vào một biến trước.

Câu hỏi thường gặp

Khai báo hằng số trong Go như thế nào?

Dùng const: const MaxRetries = 3 hoặc, với kiểu tường minh, const Timeout time.Duration = 5 * time.Second. Gộp nhiều hằng số trong một khối: const ( A = 1; B = 2 ). Giá trị phải tính được lúc biên dịch.

Có tạo được slice, map hay mảng hằng trong Go không?

Không. Hằng số chỉ có thể là boolean, số (kể cả rune) và chuỗi. const Colors = []string{"red"} báo lỗi is not constant. Hãy dùng một var ở cấp package, và nếu bên gọi không được sửa nó, hãy cung cấp một hàm trả về một bản sao mới.

Hằng số không có kiểu trong Go là gì?

Là một hằng số khai báo không kèm kiểu, như const Pi = 3.14159. Nó không có kiểu cố định cho đến khi được dùng, nên cùng một hằng số dùng được như float32, float64 hoặc, nếu giá trị cho phép, int. Hằng số không có kiểu còn chính xác tuyệt đối: trình biên dịch giữ ít nhất 256 bit độ chính xác, nên const Big = 1 << 100 hợp lệ miễn là bạn chỉ dùng nó trong các biểu thức có kết quả vừa với kiểu đích.

const và var trong Go khác nhau thế nào?

Một const được cố định lúc biên dịch và không bao giờ thay đổi; nó không chiếm bộ nhớ lúc chạy và chỉ chứa được bool, số hoặc chuỗi. Một var là một vùng nhớ có thể gán lại và chứa được mọi kiểu, kể cả các giá trị tính lúc chạy như time.Now().

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu