1 Giới thiệu về Các Loại Dữ Liệu trong Ngôn Ngữ Go

Trong ngôn ngữ Go, các loại dữ liệu đóng vai trò là nền tảng của lập trình, xác định hình thức dữ liệu mà biến có thể lưu trữ. Các loại dữ liệu cơ bản được cung cấp bởi ngôn ngữ Go chủ yếu được chia thành các danh mục sau:

Loại Dữ Liệu Mô Tả Bộ Nhớ Sử Dụng
bool Loại Boolean, được sử dụng để lưu trữ true hoặc false 1 byte
int, uint Số nguyên có dấu và không dấu, kích thước mặc định phụ thuộc vào nền tảng hệ thống 4 hoặc 8 bytes
int8, uint8 Số nguyên có dấu và không dấu 8-bit 1 byte
int16, uint16 Số nguyên có dấu và không dấu 16-bit 2 bytes
int32, uint32 Số nguyên có dấu và không dấu 32-bit 4 bytes
int64, uint64 Số nguyên có dấu và không dấu 64-bit 8 bytes
float32 Số thập phân 32-bit 4 bytes
float64 Số thập phân 64-bit 8 bytes
complex64 Số phức với phần thực và ảo 32-bit 8 bytes
complex128 Số phức với phần thực và ảo 64-bit 16 bytes
byte Tương tự uint8 1 byte
rune Tương tự int32, đại diện cho một điểm mã Unicode 4 bytes
string Loại chuỗi Tùy thuộc vào độ dài của chuỗi
error Giao diện lỗi, được sử dụng để trả về thông tin lỗi Không có kích thước cố định

Các loại này có thể được chọn theo nhu cầu khác nhau, như tính toán số, xử lý văn bản hoặc kiểm soát logic.

2 Các Loại Dữ Liệu Số Nguyên

2.1 Tổng Quan về Các Loại Số Nguyên

Ngôn ngữ Go có nhiều loại số nguyên có sẵn, phân loại như sau:

  • Số nguyên có dấu: int8, int16, int32 (hoặc rune), int64int
  • Số nguyên không dấu: uint8 (hoặc byte), uint16, uint32, uint64uint

Kích thước của intuint là 4 byte trên hệ thống 32-bit và 8 byte trên hệ thống 64-bit. Phạm vi giá trị của các loại dữ liệu số nguyên được hiển thị trong bảng dưới đây:

Loại Phạm Vi Giá Trị
int8 -128 đến 127
uint8 0 đến 255
int16 -32768 đến 32767
uint16 0 đến 65535
int32 -2147483648 đến 2147483647
uint32 0 đến 4294967295
int64 -9223372036854775808 đến 9223372036854775807
uint64 0 đến 18446744073709551615

2.2 Sử Dụng Biến Số Nguyên

Cú pháp cơ bản để khai báo biến số nguyên như sau:

var tên_biến kiểu_dữ_liệu = giá_trị_khởi_tạo

Mã mẫu:

package main
import "fmt"

func main() {
    var a int = 10 // Biến số nguyên có dấu
    var b uint = 20 // Biến số nguyên không dấu
    var c int8 = -128 // Giá trị nhỏ nhất của int8
    fmt.Println(a, b, c)
}

2.3 Các Phép Toán Số Nguyên

Ngôn ngữ Go hỗ trợ các toán tử số học thông thường, như cộng (+), trừ (-), nhân (*), chia (/), và dư (%), cũng như các toán tử bitwise như AND bitwise (&), OR bitwise (|), XOR bitwise (^), dịch trái (<<), và dịch phải (>>).

package main
import "fmt"

func main() {
    x := 10
    y := 3

    // Các phép toán số học
    fmt.Println(x + y) // Cộng
    fmt.Println(x - y) // Trừ
    fmt.Println(x * y) // Nhân
    fmt.Println(x / y) // Chia
    fmt.Println(x % y) // Dư

    // Các phép toán bitwise
    fmt.Println(x & y)  // AND bitwise
    fmt.Println(x | y)  // OR bitwise
    fmt.Println(x ^ y)  // XOR bitwise
    fmt.Println(x << 1) // Dịch trái 1 bit
    fmt.Println(x >> 1) // Dịch phải 1 bit
}

3 Các Loại Dữ Liệu Số Thực Dấu Chấm Động

3.1 Tổng quan về Các Loại Dữ Liệu Dạng Dấu Phẩy Động

Trong ngôn ngữ Go, các kiểu dữ liệu dấu phẩy động bao gồm float32float64, tương ứng với dữ liệu dấu phẩy động 32-bit và 64-bit. Nói chung, khuyến nghị sử dụng float64 vì nó cung cấp phạm vi lớn hơn và độ chính xác cao hơn.

  • float32 có khoảng 23 bit quan trọng, cung cấp khoảng 7 chữ số thập phân độ chính xác.
  • float64 có khoảng 52 bit quan trọng, cung cấp khoảng 16 chữ số thập phân độ chính xác.

3.2 Sử Dụng Biến Dạng Dấu Phẩy Động

Biến dạng dấu phẩy động có thể được khai báo bằng cách cung cấp giá trị chữ và sử dụng từ khóa var:

package main
import "fmt"

func main() {
    var f1 float32 = 3.14 // Chỉ định rõ kiểu float32
    f2 := 3.14            // Tự động suy luận là kiểu float64
    fmt.Println(f1, f2)
}

3.3 Phép Toán Dấu Phẩy Động và Vấn Đề Liên Quan

Phép toán dấu phẩy động có thể dẫn đến mất độ chính xác. Thực hiện các phép toán với độ chính xác rất cao là một vấn đề phổ biến, đặc biệt là khi trừ hai số rất gần nhau.

package main
import "fmt"

func main() {
    f1 := .1
    f2 := .2
    f3 := f1 + f2
    fmt.Println(f3) // Kết quả có thể không phải là .3 như mong đợi do vấn đề độ chính xác

    // Sửa lỗi độ chính xác bằng cách sử dụng định dạng đầu ra
    fmt.Printf("%.1f\n", f3) // Kết quả sửa đổi thành .3
}

4 Kiểu Dữ Liệu Boolean

4.1 Tổng quan về Kiểu Dữ Liệu Boolean

Boolean là kiểu dữ liệu đơn giản nhất, chỉ có thể có hai giá trị: true (đúng) và false (sai). Nó đóng vai trò rất quan trọng trong các câu lệnh điều kiện và cấu trúc điều khiển vòng lặp.

4.2 Sử Dụng Biến Boolean

Khai báo và sử dụng biến boolean:

package main
import "fmt"

func main() {
    var success bool = true
    var fail bool = false
    fmt.Println("Thao tác thành công:", success)
    fmt.Println("Thao tác thất bại:", fail)
}

Giá trị boolean thường được sử dụng trong các câu lệnh điều kiện:

package main
import "fmt"

func main() {
    a := 10
    b := 20
    fmt.Println("a == b:", a == b) // false
    fmt.Println("a < b:", a < b)   // true
}

5 Kiểu Dữ Liệu Chuỗi

5.1 Tổng quan về Chuỗi

Một chuỗi là một tập hợp các ký tự. Trong ngôn ngữ Go, chuỗi là không thay đổi. Mỗi chuỗi bao gồm hai phần: một con trỏ tới mảng byte cơ bản và một độ dài. Chuỗi có thể chứa bất kỳ dữ liệu nào, bao gồm cả byte.

5.2 Sử Dụng Biến Chuỗi

Các biến chuỗi thường được khai báo bằng cặp dấu ngoặc kép " để tạo, nhưng bạn cũng có thể sử dụng dấu backtick ` để tạo chuỗi nhiều dòng:

package main
import "fmt"

func main() {
    var s1 string = "xin chào"
    s2 := "thế giới"
    s3 := `Đây là chuỗi
    nhiều dòng`
    fmt.Println(s1)
    fmt.Println(s2)
    fmt.Println(s3)
}

Sau khi chuỗi được tạo, nội dung của nó không thể được thay đổi. Thao tác sau đây là không hợp lệ và sẽ dẫn đến lỗi biên dịch:

s := "xin chào"
s[] = 'X` // Lỗi biên dịch: nội dung chuỗi không thể thay đổi

5.3 Các Phép Toán Trên Chuỗi

Chuỗi rất phổ biến và quan trọng trong lập trình. Ngôn ngữ Go cung cấp một tập hợp phong phú các hàm tích hợp để xử lý chuỗi. Dưới đây là một số phép toán thường được sử dụng.

5.3.1 Nối Chuỗi

Trong ngôn ngữ Go, bạn có thể sử dụng toán tử cộng (+) để nối chuỗi, đó là phương pháp đơn giản nhất. Ngoài ra, khi xử lý việc nối nhiều chuỗi thường xuyên, khuyến nghị sử dụng strings.Builder để có hiệu suất tốt hơn.

package main

import (
    "fmt"
    "strings"
)

func main() {
    // Nối chuỗi sử dụng +
    hello := "Xin chào, "
    world := "thế giới!"
    result := hello + world
    fmt.Println(result) // Đầu ra: Xin chào, thế giới!

    // Nối chuỗi sử dụng strings.Builder
    var sb strings.Builder
    sb.WriteString("Xin chào, ")
    sb.WriteString("thế giới!")
    fmt.Println(sb.String()) // Đầu ra: Xin chào, thế giới!
}

5.3.2 Tách Chuỗi

Tách chuỗi có thể thực hiện bằng cách sử dụng hàm strings.Split, nó sẽ tách chuỗi thành một slice dựa trên dấu phân cách được chỉ định.

package main

import (
    "fmt"
    "strings"
)

func main() {
    // Định nghĩa một chuỗi
    sentence := "Go là một ngôn ngữ lập trình mã nguồn mở"

    // Tách chuỗi theo dấu cách
    words := strings.Split(sentence, " ")
    for _, word := range words {
        fmt.Printf("%s\n", word)
    }
    // Output:
    // Go
    // là
    // một
    // ngôn
    // ngữ
    // lập
    // trình
    // mã
    // nguồn
    // mở
}

5.3.3 Truy Cập Chỉ Mục

Trong Go, một chuỗi là một chuỗi byte không thể thay đổi. Bạn có thể sử dụng chỉ mục để truy cập các byte cụ thể trong một chuỗi. Tuy nhiên, quan trọng là lưu ý rằng vì một chuỗi có thể chứa các ký tự đa byte (như các ký tự được mã hóa UTF-8), việc truy cập trực tiếp có thể không cho ra kết quả là một ký tự đơn.

package main

import "fmt"

func main() {
    s := "Xin chào, thế giới"
    for i := 0; i < len(s); i++ {
        fmt.Printf("%d: %x\n", i, s[i])
    }
    // Lưu ý: điều này sẽ xuất ra biểu diễn thập lục phân của byte, không phải là các ký tự
}

Để lặp qua chuỗi theo từng ký tự, bạn có thể sử dụng vòng lặp range.

package main

import "fmt"

func main() {
    s := "Xin chào, thế giới"
    for index, runeValue := range s {
        fmt.Printf("%d: %U '%c'\n", index, runeValue, runeValue)
    }
    // Output: chỉ mục, mã Unicode, và ký tự tương ứng cho mỗi ký tự
}

5.3.4 Lấy Độ Dài

Hàm len có thể thu được độ dài của một chuỗi, tức là độ dài của chuỗi byte cơ bản. Đối với chuỗi UTF-8, nếu bạn cần thu được số ký tự (rune), bạn nên sử dụng hàm utf8.RuneCountInString. Điều này có thể xử lý chính xác các ký tự đa byte.

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    s := "Xin chào, thế giới"
    fmt.Println("Độ dài byte:", len(s)) // Xuất độ dài byte
    fmt.Println("Độ dài ký tự:", utf8.RuneCountInString(s)) // Xuất độ dài ký tự
}

Từ ví dụ trên, chúng ta có thể thấy rằng ngôn ngữ Go cung cấp các hàm thư viện phong phú cho các thao tác chuỗi, làm cho việc xử lý chuỗi đa dạng trở nên dễ dàng. Khi viết code, quan trọng là cần chú ý đến sự phân biệt giữa byte và ký tự, đặc biệt là khi xử lý các bộ ký tự không phải là ASCII.