Bài 8 - Kiểu dữ liệu trong Go: Kiểu chuỗi


  • Trùm cuối

    Chuỗi

    Trong hai bài trước, chúng ta đã tìm hiểu về kiểu dữ liệu số và kiểu luận lý. Hôm nay chúng ta sẽ tìm hiểu về kiểu chuỗi. Một kiểu dữ liệu quan trọng trong Go.

    fd8a62c0-08b5-42aa-a7d5-b2fc2adfa22e-image.png

    Chuỗi trong Go là 1 dãy các byte bất biến. Chuỗi có thể chứa bất kỳ dữ liệu gì nhưng thường là các ký tự đọc được. Chuỗi có thể chứa các ký tự Unicode được mã hóa dưới dạng UTF-8.

    Go cung cấp hàm len để xác định số byte trong chuỗi và toán tử để truy xuất byte bất kỳ trong chuỗi như sau:

    s := "Hello Go world!" 
    fmt.Println (len(s))      // 15 
    fmt.Println (s[0], s[10]) // 72 111  ('H'  và 'o')
    

    Nếu sử dụng s[len(s)] thì khi chạy sẽ bị lỗi do truy xuất bên ngoài chuỗi vì byte cuối cùng của chuỗi có thứ tự truy xuất là len(s) -1 do chỉ số bắt đầu từ 0.

    Nếu muốn trích xuất chuỗi con, thì Go cung cấp toán tử [i:j] để lấy chuỗi từ byte thứ i đến byte thứ j-1 và chuỗi kết quả sẽ có chiều dài là j-i byte:

    s := "Hello Go world!" 
    s1 := s[6:8] 
    fmt.Println (s1)       // "Go" 
    fmt.Println (len(s1))  // 2
    

    Nếu không có tham số i, Go sẽ hiểu bạn muốn lấy từ đầu, tức hiểu ngầm i = 0. Ngược lại, thiếu j thì Go sẽ lấy đến hết chuỗi tức hiểu ngầm j = len(s):

    s := "Hello Go world!" 
    fmt.Println (s[:5])    // "Hello" 
    fmt.Println (s[6:])    // "Go world!"
    

    Lúc đầu có đề cập chuỗi trong Go là bất biến, theo nghĩa một khi chuỗi dữ liệu đã tạo, chúng ta không thể thay đổi gì ở chuỗi đó nữa cả, nội dung lẫn chiều dài. Nếu chúng ta gán s[0] = 'S' Go sẽ báo lỗi ngay lúc biên dịch. Khi muốn thay đổi chuỗi nội dung của một biến kiểu chuỗi, chúng ta gán chuỗi mới cho biến đó, chuỗi cũ sẽ được Go xử lý dọn rác sau đó. Có nhiều cách tạo chuỗi mới cho biến:

    var s string = "Hello Go world!" 
    s1 := s[6:8] 
    fmt.Println(s1)       // Kết quả: "Go" 
    s2 := s1              // Khởi tạo biến s2 với giá trị biến s1 
    fmt.Println(s2)       // Kết quả: "Go" 
    s3 = "hello " + s2    //  Gộp 2 chuỗi rồi gán kết quả cho s3 
    fmt.Println(s2)       // Kết quả: "Go" 
    fmt.Println(s3)       // Kết quả: "hello Go"
    

    Việc quy định chuỗi bất biến sẽ giúp các xử lý trên chuỗi rất tiện lợi và nhanh chóng nhờ tính an toàn dữ liệu của chuỗi. Ví dụ như ở trên s1 và s2 lúc khởi tạo cùng tham chiếu đến một phần chuỗi được tạo ra và gán cho s thay vì phải tạo chuỗi mới. Mỗi biến kiểu chuỗi sẽ lưu giữ địa chỉ bắt đầu dãy byte dữ liệu và số byte dữ liệu thuộc về nó. Còn bản thân dãy byte dữ liệu sẽ do Go quản lý khi tạo chuỗi.

    Các biến kiểu chuỗi có thể sử dụng phép so sánh bằng ==, khác != hay lớn hơn >, nhỏ hơn <. So sánh sẽ thực hiện theo từng cặp byte dữ liệu theo thứ tự từ thấp đến cao. Ví dụ:

    s1 := "hello" 
    s2 := "Hello" 
    fmt.Println(s1 == s2)  // false 
    fmt.Println(s1 != s2)  // true 
    fmt.Println(s1 > s2)   // true: 'h' (104) > 'H'(72) 
    fmt.Println(s1 < s2)   // false
    

    Có 2 cách tạo chuỗi khi khai báo biến kiểu chuỗi:

    • Dùng cặp nháy kép "..." để mô tả chuỗi văn bản. Cách này phù hợp với chuỗi ký tự đọc được. Ký tự \ kết hợp với 1 số ký tự sẽ tạo ra ký tự đặc biệt trong chuỗi văn bản. Ví dụ: \n nghĩa là xuống hàng, còn \b là khoảng trắng, v.v... hay khi cần thể hiện ký tự thông qua giá trị của nó ở hệ 8 (\ooo với o là số từ 0 đến 7) hay hệ 16 (\xhh với h là ký tự từ 0 đến F).
    • Dùng cặp nháy ngược ... để mô tả chuỗi thô. Với chuỗi thô thì các ký tự đặc biệt được tạo từ \ như trên sẽ không được xử lý. Ngoài ra thì định dạng chuỗi cũng sẽ được giữ nguyên nên có thể tạo chuỗi nhiều dòng theo định dạng mong muốn. Cách này rất phù hợp khi lưu chuỗi là mẫu HTML, json, v.v...

    Với các chuỗi Unicode như tiếng Việt thì Go mặc định lưu theo mã hóa UTF-8 nên mỗi chữ cái có thể chiếm từ 1 đến 4 byte. Khi truy xuất chuỗi theo byte bằng [i] chúng ta chỉ lấy được byte ở vị trí i, còn muốn lấy ký tự Unicode thì chúng ta cần dùng đến package unicode và unicode/utf8 hoặc chuyển về slice rune mà chúng ta sẽ tìm hiểu sau.

    Đôi khi chúng ta cần chuyển qua lại giữa chuỗi số và số. Go cung cấp một số công cụ như sau:

    • Chuyển từ số sang chuỗi số: dùng hàm Sprintf của package fmt hoặc hàm Itoa hay FormatInt hay FormatUint của package strconv:
    var i int = 1234 
    s1 := fmt.Sprintf("%d", i) 
    s2 := fmt.Sprintf("%x", i) 
    s3 := strconv.Itoa(i) 
    s4 := strconv.FormatInt(int64(i), 16) 
    fmt.Println(s1)  // "1234" 
    fmt.Println(s4)  // "4d2" là giá trị của 1234 trong hệ 16 
    fmt.Println(s3)  // "1234"
    

    fmt.Println(s4) // "4d2" là giá trị của 1234 trong hệ 16
    Dòng 5 có sử dụng int64(i) là hình thức ép kiểu dữ liệu cho biến i từ int về int64 do tham số đầu tiên hàm FormatInt yêu cầu kiểu dữ liệu Int64.

    • Chuyển từ chuỗi số sang số: dùng hàm Atoi hoặc ParseInt của gói strconv:
    s := "1234" 
    x1, _ := strconv.Atoi(s) 
    x2, _ := strconv.ParseInt(s, 10, 32) 
    s = "4d2" 
    x3, _ := strconv.ParseInt(s, 16, 32) 
    fmt.Println(x1)  // 1234 
    fmt.Println(x2)  // 1234 
    fmt.Println(x3)  // 1234  là giá trị trong hệ 10, ứng với 4d2 trong hệ 16
    

    Ở dòng 2,3 và 5 hàm Atoi và ParseInt trả về 2 giá trị, giá trị thứ 2 là lỗi, xuất hiện khi chuỗi s không phải dạng số phù hợp. Tuy nhiên để đơn giản tôi bỏ qua không xét đến biến này và Go hỗ trợ việc đó bằng cách dùng dấu gạch dưới thay thế cho biến cần khai báo.

    Bài tiếp theo chúng ta sẽ tiếp tục bàn về kiểu dữ liệu tập hợp.

    Tóm tắt

    • Kiểu chuỗi chứa một chuỗi byte dữ liệu không thay đổi sau khi đã tạo.
    • Hàm len(s) để lấy chiều dài chuỗi s và s[i] để lấy byte thứ i của s (0<= i <len(s)).
    • Toán tử s[i:j] sẽ tạo ra chuỗi con của s chứa các phần tử từ i đến j -1. s[:j] sẽ ứng với i = 0 và s[i:] ứng với j = len(s). s[:] chính là chuỗi s.
    • Gán nội dung cho chuỗi bằng cặp "" hoặc ``. Chuỗi unicode mặc định mã hóa UTF-8.
    • Dùng fmt.Sprintfstrconv.Itoa để chuyển số thành chuỗi.
    • Dùng strconv.Atoistrconv.ParseInt để chuyển chuỗi thành số.


Có thể bạn cũng quan tâm

.
DMCA.com Protection Status