Golang là một ngôn ngữ mới, được ra đời từ 2009 và là con của Google. Nó là một ngôn ngữ có hiệu năng cao, gần với C nhưng cú pháp lại đơn giản, dễ học như python. Golang được đề cao với việc xử lý các task concurentcy tốt với việc dùng go routine và chanel… Và còn rất nhiều điều để nói về Go. Mình cũng là một người mới tiếp cận, học và sử dụng Go. Vì vậy các bài viết trong series này là nơi để mình note lại các kiến thức cần chú ý trong quá trình học cũng như xây dựng sản phẩm. Chỉ là các thứ cần chú ý hơn chứ sẽ không viết về các thứ cơ bản để hướng dẫn lập trình Go.

Language Systanx

Mình sẽ đi từ các phần cơ bản lên, và đầu tiên đương nhiên là các kiểu dữ liệu trong Go. Đầu tiên là về biến trong Go.

Variables

Trong Go hay hầu hết các ngôn ngữ khác, mỗi biến trong chương trình thường có 2 loại là biến toàn cục và biến được sử dụng nội bộ trong 1 function. Để khai báo biến trong Go có 2 cách:

  • Sử dụng var Thường khi dùng var khi bạn muốn khai báo và khởi tạo biến với giá trị mặc định (zero value).
    var x int
    var b bool
    var f float64
  • Sử dụng cú pháp khai báo nhanh := Trường hợp này sẽ sử dụng để khai báo biến và gán luôn một giá trị cho biến, ví dụ như việc nhận giá trị trả về từ 1 function. Go compiler sẽ tự biết giá trị đấy thuộc kiểu nào.
    count := 10
    x := sum(a,b)
    value, err := getValue()

Có một chú ý khi sử dụng cú pháp này là các biến phía bên trái phải có ít nhất một biết được khai báo mới. Nếu các biến đều đã khai báo trước đó, giờ chỉ cần gán lại giá trị thì bạn cần dùng phép gán “=” chứ không thể dùng “:=”. Vì function trong Go có thể trả về nhiều giá trị nên để sử dụng cú pháp này thì ít nhất 1 giá trị trả về phải chưa được khai báo trước đó. Ví dụ các trường hợp sau sẽ bị lỗi khi biên dịch.

    // Khai báo biến trước đó, xong sử dụng phép gán. Không có biến nào mới khi dùng :=
    var x int
    x := 10

    // Hàm doWork() trả về (int, error)
    var x int
    var err error
    x,err := doWork()
    // Nếu bỏ 1 trong 2 dòng khai báo trước đó thì code sẽ không lỗi

Một cái cần chú ý nữa là biến trong scope nào sẽ chỉ tồn tại và sử dụng trong scope đấy. Và việc truyền biến vào các lời gọi hàm, giá trị được trả về từ khi gọi hàm… đều là truyền tham trị, nghĩa là truyền vào 1 bạn copy của giá trị của biến chứ không phải truyền tham chiếu( giá trị con trỏ trỏ). Đoạn code sau đây mình đều dùng biến x nhưng thực ra là 3 biến trong 3 scope khác nhau.

Run Code

    package main

    import (
        "fmt"
    )

    var x int = 10
    func main() {
        fmt.Printf("x = %v \n", x) // x = 10
        var x bool = false
        fmt.Printf("x = %v \n", x) // x = false
        if x := getX(); x == 3.1412 { // x này nằm trong scope của if nên nó là biến mới và khác so với 2 x trừ trước
            fmt.Printf("x = %v \n", x) // x = 3.1412
        }
        fmt.Printf("x = %v \n", x) // x = false
    }

    func getX() float64 {
        x := 3.1412
        return x
    }

Còn tiếp theo là ví dụ về việc truyền tham trị của biến trong lời gọi hàm. Mình sẽ chỉ giới thiệu qua còn chi tiết kỹ hơn mình sẽ nói ở bài sau.

    package main

    import (
        "fmt"
    )

    func main() {
        var x int = 100
        fmt.Printf("x = %v\n", x) // x = 100
        incX(x) // truyền 1 bản copy giá trị của x
        fmt.Printf("x = %v\n", x) // x = 100 do hàm incX() chỉ vừa thay đổi giá trị của 1 bản sao của x
        incX1(&x) // cũng truyền giá trị nhưng là giá trị địa chỉ ô nhớ của x
        fmt.Printf("x = %v\n", x) // x = 101 do hàm incX1() vừa thay đổi giá trị thực của x dựa theo địa chỉ ô nhớ được truyền vào
    }

    func incX(x int) int {
        x = x + 1
        return x
    }

    func incX1(x *int) int {
        *x = *x + 1
        return *x
    }

Struct

Go là một ngôn ngữ có khá nhiều điểm tương đồng với C. Với C, chúng ta sẽ thường xuyên sử dụng đến Struct và Pointer. Và với Go cũng vậy, đây cũng là 2 cái rất quan trọng và thường xuyên được sử dụng. Vì nội dùng của 2 phần này sẽ khá là nhiều nên mình sẽ tách thành các bài riêng sau.