Go không bao giờ tự ngầm chuyển đổi kiểu. Bạn chuyển giữa các kiểu tương thích bằng T(v), và giữa số với văn bản bằng package strconv. Hai kiểu chuyển đổi được tìm nhiều nhất:
strconv.Itoa ("integer to ASCII") định dạng một int thành văn bản thập phân. strconv.Atoi ("ASCII to integer") phân tích văn bản thành một int và trả về lỗi khi văn bản không phải số hợp lệ. Luôn kiểm tra lỗi đó.
Chuyển đổi bằng T(v)
Cú pháp chuyển đổi của Go trông như một lời gọi hàm mang tên kiểu đích. Nó dùng được trong ba trường hợp:
- giữa các kiểu số (
int,int64,float64,uint8, ...), - giữa
stringvà[]bytehoặc[]rune, - giữa các kiểu có chung kiểu nền (
Celsiusvàfloat64).
Đọc kỹ kết quả:
int(3.9)là3vàint(-3.9)là-3. Chuyển đổi cắt bỏ phần thập phân về phía 0; nó không làm tròn. Hãy dùngmath.Round,math.Floorhoặcmath.Ceiltrước để chọn cách xử lý.math.Roundlàm tròn số .5 ra xa số 0, nên-3.5thành-4.uint8(300)là44, vì chỉ 8 bit thấp được giữ lại (300 trừ 256), vàint8(200)là-56cũng vì lý do đó. Không có lỗi, không có panic.Celsiusvàfloat64có chung kiểu nền, nên chuyển qua lại thoải mái, nhưng phải viết tường minh.
Các giá trị được đặt trong biến là có chủ ý. Với hằng số, cùng những phép chuyển đổi đó sẽ không biên dịch được.
Hằng số được kiểm tra, biến thì không
Chuyển một hằng số không vừa với kiểu đích là lỗi biên dịch. Chuyển một biến có cùng giá trị thì lặng lẽ bị tràn vòng:
fmt.Println(uint8(300)) // compile error
x := 300
fmt.Println(uint8(x)) // 44
./main.go:6:20: constant 300 overflows uint8
Chuyển một hằng số không nguyên sang kiểu số nguyên cũng bị từ chối, trong khi cùng giá trị đó nằm trong biến thì bị cắt phần thập phân:
./main.go:7:18: cannot convert -3.9 (untyped float constant) to type int
Nếu giá trị số thực nằm ngoài phạm vi của kiểu số nguyên đích (ví dụ int64(1e20)), kết quả phụ thuộc vào cài đặt: chẳng hạn amd64 và arm64 cho ra kết quả khác nhau. Hãy kiểm tra phạm vi trước khi chuyển đổi những giá trị không đáng tin.
Chuyển số sang chuỗi
| Từ | Hàm | Ví dụ | Kết quả |
|---|---|---|---|
int | strconv.Itoa(n) | strconv.Itoa(-7) | "-7" |
int64 | strconv.FormatInt(n, base) | strconv.FormatInt(255, 16) | "ff" |
uint64 | strconv.FormatUint(n, base) | strconv.FormatUint(5, 2) | "101" |
float64 | strconv.FormatFloat(f, fmt, prec, 64) | strconv.FormatFloat(3.14159, 'f', 2, 64) | "3.14" |
bool | strconv.FormatBool(b) | strconv.FormatBool(true) | "true" |
| mọi kiểu | fmt.Sprint(v) | fmt.Sprint(3.5) | "3.5" |
| mọi kiểu, có định dạng | fmt.Sprintf(format, v) | fmt.Sprintf("%05d", 42) | "00042" |
FormatFloat nhận một byte định dạng ('f' cho dạng thập phân cố định, 'e' cho dạng số mũ, 'g' cho dạng nào ngắn hơn), một độ chính xác (-1 nghĩa là "ít chữ số nhất mà vẫn khôi phục chính xác được"), và kích thước bit của số thực gốc (64 hoặc 32).
strconv nhanh hơn fmt.Sprint, vì fmt.Sprint đóng gói giá trị vào một interface và đi qua đường định dạng tổng quát của fmt. Trong một vòng lặp chạy nhiều, khác biệt này thấy rõ; với những lần chuyển đổi thỉnh thoảng, chọn cách nào dễ đọc hơn là được.
Chuyển chuỗi sang số
| Sang | Hàm | Trả về |
|---|---|---|
int | strconv.Atoi(s) | int, error |
| mọi kiểu số nguyên có dấu | strconv.ParseInt(s, base, bitSize) | int64, error |
| mọi kiểu số nguyên không dấu | strconv.ParseUint(s, base, bitSize) | uint64, error |
| số thực | strconv.ParseFloat(s, bitSize) | float64, error |
| bool | strconv.ParseBool(s) | bool, error |
ParseInt luôn trả về int64. Đối số bitSize (0, 8, 16, 32, 64) quy định phạm vi nó chấp nhận, nên kết quả chắc chắn vừa khi bạn chuyển xuống kiểu nhỏ hơn. base bằng 0 nghĩa là "suy ra từ tiền tố", nên 0x1f, 0o17, 0b101 và 1_000 đều phân tích được.
Những điều đầu ra cho thấy:
" 42"thất bại.Atoichỉ chấp nhận dấu tùy chọn và chữ số, không gì khác. Đầu vào từbufiothường kết thúc bằng\n, nên hãy cắt nó đi."4.2"không hợp lệ vớiAtoi. DùngParseFloatcho số thập phân.- Khi gặp lỗi vượt phạm vi,
ParseInttrả về giá trị lớn nhất còn vừa (127vớiint8) kèm theo lỗi. Đừng dùng giá trị đó khi lỗi khác nil. - Các lỗi là giá trị
*strconv.NumErrorbọcstrconv.ErrSyntaxhoặcstrconv.ErrRange, nênerrors.Isphân biệt được chúng.
ParseBool chấp nhận 1, t, T, TRUE, true, True và các dạng false tương ứng. "yes" và "on" là lỗi.
Cái bẫy string(65)
Chuyển một số nguyên sang string không tạo ra chữ số. Nó tạo ra ký tự có code point Unicode đó:
n := 65
s := string(n) // "A", not "65"
Trình biên dịch chấp nhận, nhưng go vet sẽ báo:
./main.go:10:7: conversion from int to string yields a string of one rune, not a string of digits
Nếu bạn thật sự muốn lấy ký tự, hãy nói rõ bằng rune: string(rune(n)). Nếu bạn muốn con số dưới dạng văn bản, dùng strconv.Itoa(n). Chuyển một rune hoặc byte sang string thì hoàn toàn bình thường và rất hay gặp:
Chuỗi, byte và rune
string, []byte và []rune chuyển qua lại với nhau bằng T(v). Mỗi lần chuyển đổi đều sao chép dữ liệu, vì chuỗi là bất biến còn slice thì không:
Chuyển sang []rune khi bạn cần truy cập hoặc sửa theo từng ký tự; chuyển sang []byte cho I/O, băm và chỉnh sửa ở mức byte. Trang rune và byte trình bày chi tiết về UTF-8.
Chuyển đổi kiểu và type assertion
Chuyển đổi kiểu biến một giá trị từ kiểu cụ thể này sang kiểu cụ thể khác. Lấy giá trị cụ thể ra khỏi một interface (any, error, io.Reader) là một thao tác khác, gọi là type assertion:
var v any = 42
n := v.(int) // type assertion: v holds an int, give it to me
f := float64(n) // conversion: int to float64
// f := float64(v) // compile error: cannot convert v (variable of interface type any)
// // to type float64: need type assertion
Assert trước, rồi mới chuyển đổi. Dạng comma-ok n, ok := v.(int) tránh được panic khi kiểu không đúng.
Tra cứu nhanh
| Việc cần làm | Code |
|---|---|
| int sang string | strconv.Itoa(n) |
| int64 sang string | strconv.FormatInt(n, 10) |
| string sang int | n, err := strconv.Atoi(s) |
| string sang int64 | n, err := strconv.ParseInt(s, 10, 64) |
| string sang float64 | f, err := strconv.ParseFloat(s, 64) |
| float64 sang string | strconv.FormatFloat(f, 'f', -1, 64) |
| float64 sang int | int(f) (cắt phần thập phân) hoặc int(math.Round(f)) |
| int sang float64 | float64(n) |
| string sang []byte | []byte(s) |
| []byte sang string | string(b) |
| rune sang string | string(r) |
| bool sang string | strconv.FormatBool(b) |
| mọi giá trị sang string | fmt.Sprint(v) |
Câu hỏi thường gặp
Chuyển int sang string trong Go như thế nào?
Dùng strconv.Itoa(n): strconv.Itoa(42) trả về "42". Với int64, dùng strconv.FormatInt(n, 10). fmt.Sprint(n) cũng dùng được với mọi kiểu nhưng chậm hơn. Đừng viết string(n): cách đó coi n là một code point Unicode, nên string(65) ra "A".
Chuyển string sang int trong Go như thế nào?
Dùng strconv.Atoi, hàm này trả về con số và một lỗi:
n, err := strconv.Atoi("42")
if err != nil {
// not a valid integer
}
Nếu cần kích thước hoặc cơ số cụ thể, dùng strconv.ParseInt(s, 10, 64), hàm này trả về int64. Atoi không tự cắt khoảng trắng, nên " 42" là lỗi; hãy gọi strings.TrimSpace trước.
Ép kiểu trong Go như thế nào?
Go gọi đó là chuyển đổi kiểu (conversion), và cú pháp là T(v): float64(n), int(f), []byte(s). Nó dùng được giữa các kiểu số, giữa chuỗi với slice byte hoặc rune, và giữa các kiểu có cùng kiểu nền. Nó không phân tích văn bản: biến "42" thành 42 cần đến strconv. Lấy kiểu cụ thể ra khỏi một interface là type assertion, v.(T), chứ không phải chuyển đổi kiểu.
Chuyển float sang int trong Go như thế nào?
int(f) cắt bỏ phần thập phân về phía 0: int(3.9) là 3 và int(-3.9) là -3. Để làm tròn, dùng int(math.Round(f)). Nếu số thực nằm ngoài phạm vi của kiểu số nguyên, kết quả phụ thuộc vào nền tảng, nên hãy kiểm tra phạm vi trước khi giá trị đến từ đầu vào.
Vì sao string(65) trong Go trả về "A"?
Chuyển một số nguyên sang string sẽ hiểu số đó là một code point Unicode, và 65 là code point của A. go vet sẽ cảnh báo: conversion from int to string yields a string of one rune, not a string of digits. Dùng strconv.Itoa(65) để có "65".