Chuỗi (string) xứng đáng được đề cập đặc biệt trong Go vì chúng khác biệt trong cách triển khai khi so sánh với các ngôn ngữ khác.
Chuỗi là một slice của byte trong Go. String có thể được tạo bằng cách đặt một tập hợp các ký tự bên trong cặp dấu ngoặc kép ""
.
Hãy xem một ví dụ đơn giản tạo một string
và in nó.
package main
import (
"fmt"
)
func main() {
name := "Hello World"
fmt.Println(name)
}
Chạy chương trình trong playground
Chương trình trên sẽ in ra:
Hello World
String trong Go tuân thủ Unicode và được mã hóa UTF-8.
Vì một string là một slice của byte, nên có thể truy cập từng byte của một string.
package main
import (
"fmt"
)
func printBytes(s string) {
fmt.Printf("Bytes: ")
for i := 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
func main() {
name := "Hello World"
fmt.Printf("String: %s\n", name)
printBytes(name)
}
Chạy chương trình trong playground
%s là mã định dạng để in một chuỗi. Trong dòng số 16, chuỗi đầu vào được in. Trong dòng số 9 của chương trình trên, len(s) trả về số byte trong chuỗi và chúng ta sử dụng vòng lặp for để in các byte đó dưới dạng ký hiệu thập lục phân. %x là mã định dạng cho hệ thập lục phân. Kết quả chương trình trên:
String: Hello World
Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64
Đây là các giá trị được mã hóa Unicode UT8 của chuỗi Hello World
. Cần có hiểu biết cơ bản về Unicode và UTF-8 để hiểu các chuỗi tốt hơn. Tôi khuyên bạn nên đọc bài viết dưới đây để biết thêm về Unicode và UTF-8.
Hãy sửa đổi chương trình trên một chút để in các ký tự của chuỗi.
package main
import (
"fmt"
)
func printBytes(s string) {
fmt.Printf("Bytes: ")
for i := 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
func printChars(s string) {
fmt.Printf("Characters: ")
for i := 0; i < len(s); i++ {
fmt.Printf("%c ", s[i])
}
}
func main() {
name := "Hello World"
fmt.Printf("String: %s\n", name)
printChars(name)
fmt.Printf("\n")
printBytes(name)
}
Chạy chương trình trong playground
Trong dòng 17 của chương trình trên, mã định dạng %c được sử dụng để in các ký tự của chuỗi trong hàm printChars
. Chương trình in ra kết quả sau:
String: Hello World
Characters: H e l l o W o r l d
Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64
Mặc dù chương trình trên trông giống như một cách hợp pháp để truy cập các ký tự riêng lẻ của một chuỗi, nhưng điều này có một lỗi nghiêm trọng. Hãy cùng tìm hiểu lỗi đó là gì.
package main
import (
"fmt"
)
func printBytes(s string) {
fmt.Printf("Bytes: ")
for i := 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
func printChars(s string) {
fmt.Printf("Characters: ")
for i := 0; i < len(s); i++ {
fmt.Printf("%c ", s[i])
}
}
func main() {
name := "Hello World"
fmt.Printf("String: %s\n", name)
printChars(name)
fmt.Printf("\n")
printBytes(name)
fmt.Printf("\n\n")
name = "Señor"
fmt.Printf("String: %s\n", name)
printChars(name)
fmt.Printf("\n")
printBytes(name)
}
Chạy chương trình trong playground
Kết quả của chương trình trên là:
String: Hello World
Characters: H e l l o W o r l d
Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64
String: Señor
Characters: S e à ± o r
Bytes: 53 65 c3 b1 6f 72
Trong dòng 30 của chương trình trên, chúng ta đang cố gắng in các ký tự của Señor và nó in ra S e à ± o r là không chính xác. Tại sao chương trình này bị hỏng với Señor
trong khi nó hoạt động hoàn toàn tốt cho Hello World
.
Lý do là mã Unicode của ñ
là U+00F1
và mã hóa UTF-8 của nó chiếm 2 byte c3
và b1
. Chúng ta đang cố gắng in các ký tự giả sử rằng mỗi mã sẽ dài một byte, điều này là sai.
Trong mã hóa UTF-8, một mã có thể chiếm nhiều hơn 1 byte. Vậy chúng ta giải quyết điều này như thế nào? Đây là nơi mà rune cứu chúng ta.
Rune là một kiểu dữ liệu tích hợp sẵn trong Go và nó là bí danh của int32. Rune đại diện cho một mã Unicode trong Go. Không quan trọng mã chiếm bao nhiêu byte, nó có thể được biểu thị bằng rune. Hãy sửa đổi chương trình trên để in các ký tự bằng rune.
package main
import (
"fmt"
)
func printBytes(s string) {
fmt.Printf("Bytes: ")
for i := 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
}
func printChars(s string) {
fmt.Printf("Characters: ")
runes := []rune(s)
for i := 0; i < len(runes); i++ {
fmt.Printf("%c ", runes[i])
}
}
func main() {
name := "Hello World"
fmt.Printf("String: %s\n", name)
printChars(name)
fmt.Printf("\n")
printBytes(name)
fmt.Printf("\n\n")
name = "Señor"
fmt.Printf("String: %s\n", name)
printChars(name)
fmt.Printf("\n")
printBytes(name)
}
Chạy chương trình trong playground
Trong dòng 16 của chương trình trên, chuỗi được chuyển đổi thành một slice kiểu rune. Sau đó, chúng ta duyệt qua nó và hiển thị các ký tự. Chương trình này in ra:
String: Hello World
Characters: H e l l o W o r l d
Bytes: 48 65 6c 6c 6f 20 57 6f 72 6c 64
String: Señor
Characters: S e ñ o r
Bytes: 53 65 c3 b1 6f 72
Thật hoàn hảo😀.
Chương trình trên là một cách hoàn hảo để duyệt qua các rune riêng lẻ của một chuỗi. Nhưng Go cung cấp cho chúng ta một cách dễ dàng hơn nhiều để thực hiện việc này bằng cách sử dụng vòng lặp for range.
package main
import (
"fmt"
)
func charsAndBytePosition(s string) {
for index, rune := range s {
fmt.Printf("%c starts at byte %d\n", rune, index)
}
}
func main() {
name := "Señor"
charsAndBytePosition(name)
}
Chạy chương trình trong playground
Trong dòng 8 của chương trình trên, chuỗi được duyệt qua bằng cách sử dụng vòng lặp for range
. Vòng lặp trả về vị trí của byte mà rune bắt đầu cùng với rune. Chương trình này xuất ra:
S starts at byte 0
e starts at byte 1
ñ starts at byte 2
o starts at byte 4
r starts at byte 5
Từ đầu ra ở trên, rõ ràng là ñ
chiếm 2 byte vì ký tự tiếp theo o
bắt đầu ở byte 4 thay vì byte 3 😀.
package main
import (
"fmt"
)
func main() {
byteSlice := []byte{0x43, 0x61, 0x66, 0xC3, 0xA9}
str := string(byteSlice)
fmt.Println(str)
}
Chạy chương trình trong playground
Slice byteSlice
ở dòng số 8 của chương trình trên chứa các byte hex được mã hóa UTF-8 của chuỗi Café
. Chương trình in ra kết quả sau:
Café
Điều gì sẽ xảy ra nếu chúng ta có giá trị thập phân tương đương với các giá trị hex. Liệu chương trình trên có hoạt động không? Hãy cùng kiểm tra nào.
package main
import (
"fmt"
)
func main() {
byteSlice := []byte{67, 97, 102, 195, 169}//decimal equivalent of {'\x43', '\x61', '\x66', '\xC3', '\xA9'}
str := string(byteSlice)
fmt.Println(str)
}
Chạy chương trình trong playground
Các giá trị thập phân cũng hoạt động và chương trình trên cũng sẽ in ra:
Café
package main
import (
"fmt"
)
func main() {
runeSlice := []rune{0x0053, 0x0065, 0x00f1, 0x006f, 0x0072}
str := string(runeSlice)
fmt.Println(str)
}
Chạy chương trình trong playground
Trong chương trình trên slice runeSlice
có chứa các mã Unicode của chuỗi Señor
trong hệ thập lục phân. Kết quả chương trình
Señor
Hàm RuneCountInString(s string) (n int)
của gói utf8 có thể được sử dụng để tìm độ dài của chuỗi. Hàm này nhận một chuỗi làm đối số và trả về số lượng rune trong đó.
Như chúng ta đã thảo luận trước đó, len(s)
được sử dụng để tìm số byte trong chuỗi và nó không trả về độ dài chuỗi. Như chúng ta đã thảo luận, một số ký tự Unicode có các điểm mã chiếm hơn 1 byte. Sử dụng len
để tìm ra độ dài của các chuỗi đó sẽ trả về độ dài chuỗi không chính xác.
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
word1 := "Señor"
fmt.Printf("String: %s\n", word1)
fmt.Printf("Length: %d\n", utf8.RuneCountInString(word1))
fmt.Printf("Number of bytes: %d\n", len(word1))
fmt.Printf("\n")
word2 := "Pets"
fmt.Printf("String: %s\n", word2)
fmt.Printf("Length: %d\n", utf8.RuneCountInString(word2))
fmt.Printf("Number of bytes: %d\n", len(word2))
}
Chạy chương trình trong playground
Kết quả của chương trình trên là:
String: Señor
Length: 5
Number of bytes: 6
String: Pets
Length: 4
Number of bytes: 4
Kết quả ở trên xác nhận hàm len(s)
và RuneCountInString(s)
trả về các giá trị khác nhau 😀.
Toán tử ==
được sử dụng để so sánh bằng hai chuỗi. Nếu cả hai chuỗi bằng nhau, thì kết quả là true
, ngược lại là false
.
package main
import (
"fmt"
)
func compareStrings(str1 string, str2 string) {
if str1 == str2 {
fmt.Printf("%s and %s are equal\n", str1, str2)
return
}
fmt.Printf("%s and %s are not equal\n", str1, str2)
}
func main() {
string1 := "Go"
string2 := "Go"
compareStrings(string1, string2)
string3 := "hello"
string4 := "world"
compareStrings(string3, string4)
}
Chạy chương trình trong playground
Trong hàm compareStrings
trên, dòng số 8 so sánh xem hai chuỗi str1
và str2
có bằng nhau hay không bằng cách sử dụng toán tử ==
. Nếu chúng bằng nhau, nó sẽ in ra một thông báo tương ứng và thoát khỏi hàm.
Chương trình trên in ra:
Go and Go are equal
hello and world are not equal
Có nhiều cách để thực hiện nối chuỗi trong Go. Hãy tham khảo một vài cách trong số chúng.
Cách đơn giản nhất để thực hiện nối chuỗi là sử dụng toán tử +
.
package main
import (
"fmt"
)
func main() {
string1 := "Go"
string2 := "is awesome"
result := string1 + " " + string2
fmt.Println(result)
}
Chạy chương trình trong playground
Trong chương trình trên, ở dòng 10, string1
được nối string2
với một khoảng trắng ở giữa. Chương trình này in ra:
Go is awesome
Cách thứ hai để nối các chuỗi là sử dụng hàm Sprintf của gói fmt.
Hàm Sprintf
nối các chuỗi theo định dạng đầu vào và trả về chuỗi kết quả. Hãy viết lại chương trình trên bằng hàm Sprintf
.
package main
import (
"fmt"
)
func main() {
string1 := "Go"
string2 := "is awesome"
result := fmt.Sprintf("%s %s", string1, string2)
fmt.Println(result)
}
Chạy chương trình trong playground
Trong dòng số 10 của chương trình trên, %s %s
là đầu vào định dạng cho Sprintf
. Định dạng này lấy hai chuỗi làm đầu vào và có một khoảng trắng ở giữa. Điều này sẽ nối hai chuỗi với một khoảng trắng ở giữa. Chuỗi kết quả được lưu trữ trong result
. Chương trình này cũng in ra kết quả:
Go is awesome
Các chuỗi là bất biến trong Go. Sau khi một chuỗi được tạo, bạn không thể thay đổi nó.
package main
import (
"fmt"
)
func mutate(s string)string {
s[0] = 'a'//any valid unicode character within single quote is a rune
return s
}
func main() {
h := "hello"
fmt.Println(mutate(h))
}
Chạy chương trình trong playground
Trong dòng số 8 của chương trình trên, chúng ta cố gắng thay đổi ký tự đầu tiên của chuỗi thành 'a'
. Bất kỳ ký tự Unicode hợp lệ nào trong một dấu ngoặc kép đều là rune.
Chúng ta cố gắng gán rune a
vào vị trí thứ 0 của slice. Điều này không được phép vì chuỗi là bất biến và do đó chương trình không biên dịch được với lỗi:
./prog.go:8:7: cannot assign to s[0]
Để làm việc với chuỗi bất biến, chuỗi cần được chuyển đổi thành một slice của rune. Sau đó, có thể thay đổi các phần tử trong slice và được chuyển đổi trở lại thành một chuỗi mới.
package main
import (
"fmt"
)
func mutate(s []rune) string {
s[0] = 'a'
return string(s)
}
func main() {
h := "hello"
fmt.Println(mutate([]rune(h)))
}
Chạy chương trình trong playground
Trong dòng số 7 của chương trình trên, hàm mutate
chấp nhận một slice của rune làm đối số. Sau đó, nó thay đổi phần tử đầu tiên của slice thành 'a'
, chuyển đổi rune trở lại thành chuỗi và trả về nó.
Hàm này được gọi từ dòng số 13 của chương trình. Chuỗi h
được chuyển đổi thành một slice của rune và được truyền đến hàm mutate
tại dòng số 13. Chương trình này xuất kết quả sau:
aello
Như thường lệ, cảm ơn bạn đã đọc. Hãy chia sẻ những nhận xét và phản hồi có giá trị của bạn.
Bạn có thể vui lòng tắt trình chặn quảng cáo ❤️ để hỗ trợ chúng tôi duy trì hoạt động của trang web.
Trong hướng dẫn này, chúng ta sẽ tìm hiểu phương thức (method) trong Go là gì? Cú pháp khai báo phương thức, so sánh phương thức với hàm, ... trong Go.
Trong hướng dẫn này, chúng ta sẽ tìm hiểu struct là gì, cách khai báo và sử dụng một struct trong Go, struct ẩn danh, so sanh hai struct, ...
Trong hướng dẫn này, chúng ta sẽ tìm hiểu cách con trỏ (pointer) hoạt động trong Go và nó khác với con trỏ trong các ngôn ngữ khác như C và C++ như thế nào.
Trong hướng dẫn này, chúng ta sẽ tìm hiểu map là gì, cú pháp khai báo map, thêm phần tử, xóa phần tử, duyệt các phần tử, ... của map trong Go.