☰ Mục lục series
Tài liệu Go tiếng Việt
  1. Bắt đầu (Getting started)
    1. Tổng quan
    2. Tải về và cài đặt
    3. Bắt đầu với Go
    4. Quản lý các bản cài đặt Go
    5. Plugin trình soạn thảo và IDE
  2. Dạo quanh Go (A Tour of Go)
    1. Chào mừng
    2. Package, biến và hàm
    3. Điều khiển luồng
    4. Struct, slice và map
    5. Method và interface
    6. Generics
    7. Xử lý đồng thời
  3. Hướng dẫn (Tutorials)
    1. Danh sách hướng dẫn
    2. Tạo một Go module
    3. Gọi mã của bạn từ một module khác
    4. Trả về và xử lý lỗi
    5. Trả về lời chào ngẫu nhiên
    6. Trả về lời chào cho nhiều người
    7. Thêm một test
    8. Biên dịch và cài đặt ứng dụng
    9. Kết luận
    10. Bắt đầu với workspace nhiều module
    11. Phát triển RESTful API với Go và Gin
    12. Truy cập cơ sở dữ liệu quan hệ
    13. Bắt đầu với generics
    14. Bắt đầu với fuzzing
    15. Làm việc với JSON
    16. Tìm và sửa các dependency có lỗ hổng với govulncheck
    17. Tìm và khắc phục phụ thuộc có lỗ hổng với VS Code Go
    18. Viết ứng dụng web
  4. Viết code Go
    1. Cách viết mã Go
    2. Effective Go
    3. Doc Comment trong Go
  5. Module
    1. Phát triển và publish module
    2. Quản lý phụ thuộc
    3. Tổ chức một module Go
    4. Quản lý mã nguồn của module
    5. Cách đánh số phiên bản module
    6. Quy trình phát hành và đánh phiên bản module
    7. Publish một module
    8. Phát triển một bản cập nhật major version
    9. Tham chiếu tệp go.mod
    10. Ngừng hỗ trợ 'go get' cho việc cài đặt tệp thực thi
  6. Cơ sở dữ liệu (Database)
    1. Truy cập cơ sở dữ liệu quan hệ
    2. Mở một handle cơ sở dữ liệu
    3. Thực thi câu lệnh SQL không trả về dữ liệu
    4. Truy vấn dữ liệu
    5. Dùng prepared statement
    6. Thực thi transaction
    7. Huỷ các thao tác đang chạy
    8. Quản lý kết nối
    9. Tránh rủi ro SQL injection
  7. Chẩn đoán và hiệu năng
    1. Chẩn đoán
    2. Hướng dẫn về Go Garbage Collector
    3. Tối ưu hoá dựa trên profile
    4. Data Race Detector
    5. Gỡ lỗi mã Go bằng GDB
    6. Hỗ trợ coverage profiling cho integration test
  8. Bảo mật (Security)
    1. Tổng quan bảo mật
    2. Thực hành tốt về bảo mật cho lập trình viên Go
    3. Quản lý lỗ hổng trong Go
    4. Cơ sở dữ liệu lỗ hổng của Go
    5. Quét lỗ hổng trong IDE
    6. Chính sách CNA của Go
    7. Go Fuzzing
    8. Chi tiết kỹ thuật của Go Fuzzing
    9. Chính sách bảo mật của Go
    10. Threat Model của Go
    11. Các quyết định về bảo mật của Go
    12. Tuân thủ FIPS 140-3
  9. Công cụ và toolchain
    1. Tài liệu về các lệnh
    2. Về lệnh go
    3. Go Toolchains
    4. Telemetry của Go
    5. Cài đặt Go từ mã nguồn
    6. Cài đặt và sử dụng gccgo
  10. Tham khảo
    1. Câu hỏi thường gặp (FAQ)
    2. Go 1 và tương lai của các chương trình Go
    3. Hướng dẫn chuyển đổi sang encoding/json/v2
    4. Hướng dẫn đóng góp
    5. Đóng góp cho frontend gccgo
  11. Phụ lục
    1. Bảng thuật ngữ
Dạo quanh Go (A Tour of Go)

Thêm về kiểu dữ liệu: struct, slice và map

Bản dịch từ go.dev/tour/moretypes/1

Pointer

Go có pointer (con trỏ). Một pointer giữ địa chỉ bộ nhớ của một giá trị.

Kiểu *T là pointer trỏ tới một giá trị kiểu T. Giá trị zero của nó là nil.

var p *int

Toán tử & tạo ra một pointer trỏ tới toán hạng của nó.

i := 42
p = &i

Toán tử * cho bạn giá trị mà pointer đang trỏ tới.

fmt.Println(*p) // read i through the pointer p
*p = 21         // set i through the pointer p

Việc này gọi là “dereferencing” (giải tham chiếu) hay “indirecting”.

Khác với C, Go không có phép toán số học trên pointer.

package main

import "fmt"

func main() {
	i, j := 42, 2701

	p := &i         // point to i
	fmt.Println(*p) // read i through the pointer
	*p = 21         // set i through the pointer
	fmt.Println(i)  // see the new value of i

	p = &j         // point to j
	*p = *p / 37   // divide j through the pointer
	fmt.Println(j) // see the new value of j
}

Chạy thử ví dụ này trong Tour of Go ↗

Struct

Một struct là một tập hợp các trường (field).

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	fmt.Println(Vertex{1, 2})
}

Chạy thử ví dụ này trong Tour of Go ↗

Trường của struct

Bạn truy cập các trường của struct bằng dấu chấm.

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	v.X = 4
	fmt.Println(v.X)
}

Chạy thử ví dụ này trong Tour of Go ↗

Pointer tới struct

Bạn cũng có thể truy cập trường của struct thông qua một pointer tới struct đó.

Khi có pointer p trỏ tới một struct và muốn lấy trường X, ta có thể viết (*p).X. Nhưng cách viết đó khá rườm rà, nên Go cho phép viết gọn thành p.X, không cần giải tham chiếu tường minh.

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	p := &v
	p.X = 1e9
	fmt.Println(v)
}

Chạy thử ví dụ này trong Tour of Go ↗

Struct literal

Một struct literal tạo ra một giá trị struct mới bằng cách liệt kê giá trị cho các trường của nó.

Bạn có thể chỉ liệt kê một phần các trường bằng cú pháp Name:. (Và thứ tự các trường được đặt tên không quan trọng.)

Tiền tố đặc biệt & trả về một pointer tới giá trị struct đó.

package main

import "fmt"

type Vertex struct {
	X, Y int
}

var (
	v1 = Vertex{1, 2}  // has type Vertex
	v2 = Vertex{X: 1}  // Y:0 is implicit
	v3 = Vertex{}      // X:0 and Y:0
	p  = &Vertex{1, 2} // has type *Vertex
)

func main() {
	fmt.Println(v1, p, v2, v3)
}

Chạy thử ví dụ này trong Tour of Go ↗

Array

Kiểu [n]T là một array (mảng) gồm n giá trị kiểu T.

Biểu thức

var a [10]int

khai báo biến a là một array gồm mười số nguyên.

Độ dài của array là một phần trong kiểu của nó, nên array không đổi kích thước được. Nghe có vẻ gò bó, nhưng bạn đừng lo; Go có một cách rất tiện để làm việc với array.

package main

import "fmt"

func main() {
	var a [2]string
	a[0] = "Hello"
	a[1] = "World"
	fmt.Println(a[0], a[1])
	fmt.Println(a)

	primes := [6]int{2, 3, 5, 7, 11, 13}
	fmt.Println(primes)
}

Chạy thử ví dụ này trong Tour of Go ↗

Slice

Array có kích thước cố định. Còn slice (danh sách co giãn được) là một khung nhìn linh hoạt, kích thước thay đổi được, vào các phần tử của một array. Trong thực tế, slice được dùng nhiều hơn array rất nhiều.

Kiểu []T là một slice có các phần tử kiểu T.

Bạn tạo một slice bằng cách chỉ ra hai chỉ số, cận dưới và cận trên, ngăn cách bởi dấu hai chấm:

a[low : high]

Cách này chọn một khoảng nửa mở: bao gồm phần tử đầu, nhưng không bao gồm phần tử cuối.

Biểu thức sau tạo một slice gồm các phần tử từ 1 đến 3 của a:

a[1:4]
package main

import "fmt"

func main() {
	primes := [6]int{2, 3, 5, 7, 11, 13}

	var s []int = primes[1:4]
	fmt.Println(s)
}

Chạy thử ví dụ này trong Tour of Go ↗

Slice giống như tham chiếu tới array

Slice không tự lưu dữ liệu, nó chỉ mô tả một đoạn của array nằm bên dưới.

Thay đổi các phần tử của slice cũng là thay đổi các phần tử tương ứng của array bên dưới.

Những slice khác dùng chung array đó sẽ thấy các thay đổi này.

package main

import "fmt"

func main() {
	names := [4]string{
		"John",
		"Paul",
		"George",
		"Ringo",
	}
	fmt.Println(names)

	a := names[0:2]
	b := names[1:3]
	fmt.Println(a, b)

	b[0] = "XXX"
	fmt.Println(a, b)
	fmt.Println(names)
}

Chạy thử ví dụ này trong Tour of Go ↗

Slice literal

Một slice literal giống array literal nhưng không có phần độ dài.

Đây là một array literal:

[3]bool{true, true, false}

Còn đây tạo ra đúng array như trên, rồi dựng một slice tham chiếu tới nó:

[]bool{true, true, false}
package main

import "fmt"

func main() {
	q := []int{2, 3, 5, 7, 11, 13}
	fmt.Println(q)

	r := []bool{true, false, true, true, false, true}
	fmt.Println(r)

	s := []struct {
		i int
		b bool
	}{
		{2, true},
		{3, false},
		{5, true},
		{7, true},
		{11, false},
		{13, true},
	}
	fmt.Println(s)
}

Chạy thử ví dụ này trong Tour of Go ↗

Giá trị mặc định khi cắt slice

Khi cắt slice, bạn có thể bỏ qua cận trên hoặc cận dưới để dùng giá trị mặc định.

Mặc định của cận dưới là 0, còn mặc định của cận trên là độ dài của slice hoặc array bên dưới.

Với array

var a [10]int

các biểu thức cắt slice sau là tương đương:

a[0:10]
a[:10]
a[0:]
a[:]
package main

import "fmt"

func main() {
	s := []int{2, 3, 5, 7, 11, 13}

	s = s[1:4]
	fmt.Println(s)

	s = s[:2]
	fmt.Println(s)

	s = s[1:]
	fmt.Println(s)
}

Chạy thử ví dụ này trong Tour of Go ↗

Length và capacity của slice

Một slice có cả length (độ dài) lẫn capacity (sức chứa).

Độ dài của slice là số phần tử nó đang chứa.

Sức chứa của slice là số phần tử của array bên dưới, tính từ phần tử đầu tiên của slice.

Bạn lấy độ dài và sức chứa của slice s bằng hai biểu thức len(s) và cap(s).

Bạn có thể nới dài một slice bằng cách cắt lại nó, miễn là sức chứa còn đủ. Thử sửa một phép cắt slice trong chương trình ví dụ để nới slice vượt quá sức chứa và xem điều gì xảy ra.

package main

import "fmt"

func main() {
	s := []int{2, 3, 5, 7, 11, 13}
	printSlice(s)

	// Slice the slice to give it zero length.
	s = s[:0]
	printSlice(s)

	// Extend its length.
	s = s[:4]
	printSlice(s)

	// Drop its first two values.
	s = s[2:]
	printSlice(s)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

Chạy thử ví dụ này trong Tour of Go ↗

Slice nil

Giá trị zero của slice là nil.

Một slice nil có độ dài và sức chứa bằng 0, và không có array nào bên dưới.

package main

import "fmt"

func main() {
	var s []int
	fmt.Println(s, len(s), cap(s))
	if s == nil {
		fmt.Println("nil!")
	}
}

Chạy thử ví dụ này trong Tour of Go ↗

Tạo slice bằng make

Bạn có thể tạo slice bằng hàm dựng sẵn make; đây là cách tạo mảng có kích thước thay đổi được.

Hàm make cấp phát một array đã được gán giá trị zero rồi trả về một slice trỏ tới array đó:

a := make([]int, 5)  // len(a)=5

Muốn chỉ định sức chứa, truyền thêm đối số thứ ba cho make:

b := make([]int, 0, 5) // len(b)=0, cap(b)=5

b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:]      // len(b)=4, cap(b)=4
package main

import "fmt"

func main() {
	a := make([]int, 5)
	printSlice("a", a)

	b := make([]int, 0, 5)
	printSlice("b", b)

	c := b[:2]
	printSlice("c", c)

	d := c[2:5]
	printSlice("d", d)
}

func printSlice(s string, x []int) {
	fmt.Printf("%s len=%d cap=%d %v\n",
		s, len(x), cap(x), x)
}

Chạy thử ví dụ này trong Tour of Go ↗

Slice của slice

Slice có thể chứa kiểu bất kỳ, kể cả slice khác.

package main

import (
	"fmt"
	"strings"
)

func main() {
	// Create a tic-tac-toe board.
	board := [][]string{
		[]string{"_", "_", "_"},
		[]string{"_", "_", "_"},
		[]string{"_", "_", "_"},
	}

	// The players take turns.
	board[0][0] = "X"
	board[2][2] = "O"
	board[1][2] = "X"
	board[1][0] = "O"
	board[0][2] = "X"

	for i := 0; i < len(board); i++ {
		fmt.Printf("%s\n", strings.Join(board[i], " "))
	}
}

Chạy thử ví dụ này trong Tour of Go ↗

Thêm phần tử vào slice bằng append

Việc thêm phần tử mới vào slice rất hay gặp, nên Go có sẵn hàm dựng sẵn append. Tài liệu của package builtin mô tả append.

func append(s []T, vs ...T) []T

Tham số đầu tiên s của append là một slice kiểu T, các tham số còn lại là những giá trị T cần thêm vào slice.

Kết quả của append là một slice chứa tất cả phần tử của slice ban đầu cộng thêm các giá trị bạn đưa vào.

Nếu array nằm dưới s quá nhỏ, không chứa hết được các giá trị mới, Go sẽ cấp phát một array lớn hơn. Slice trả về khi đó trỏ tới array vừa được cấp phát.

(Muốn hiểu sâu hơn về slice, hãy đọc bài Slices: usage and internals.)

package main

import "fmt"

func main() {
	var s []int
	printSlice(s)

	// append works on nil slices.
	s = append(s, 0)
	printSlice(s)

	// The slice grows as needed.
	s = append(s, 1)
	printSlice(s)

	// We can add more than one element at a time.
	s = append(s, 2, 3, 4)
	printSlice(s)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

Chạy thử ví dụ này trong Tour of Go ↗

Range

Dạng range của vòng lặp for duyệt qua một slice hoặc một map.

Khi duyệt một slice, mỗi vòng lặp trả về hai giá trị. Giá trị đầu là chỉ số, giá trị sau là bản sao của phần tử tại chỉ số đó.

package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
	for i, v := range pow {
		fmt.Printf("2**%d = %d\n", i, v)
	}
}

Chạy thử ví dụ này trong Tour of Go ↗

Range (tiếp)

Bạn có thể bỏ qua chỉ số hoặc giá trị bằng cách gán vào _.

for i, _ := range pow for _, value := range pow

Nếu chỉ cần chỉ số, bạn có thể lược bỏ luôn biến thứ hai.

for i := range pow

package main

import "fmt"

func main() {
	pow := make([]int, 10)
	for i := range pow {
		pow[i] = 1 << uint(i) // == 2**i
	}
	for _, value := range pow {
		fmt.Printf("%d\n", value)
	}
}

Chạy thử ví dụ này trong Tour of Go ↗

Bài tập: Slice

Hãy hiện thực hàm Pic. Hàm này trả về một slice có độ dài dy, mỗi phần tử lại là một slice gồm dx số nguyên không dấu 8 bit. Khi chạy chương trình, nó sẽ hiển thị bức ảnh của bạn, hiểu các số đó như mức xám (thật ra là mức xanh dương).

Vẽ ảnh gì là tuỳ bạn. Vài hàm thú vị có thể dùng: (x+y)/2, x*y, và x^y.

(Bạn cần một vòng lặp để cấp phát từng []uint8 bên trong [][]uint8.)

(Dùng uint8(intValue) để chuyển đổi kiểu.)

package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
}

func main() {
	pic.Show(Pic)
}

Chạy thử ví dụ này trong Tour of Go ↗

Map

Một map ánh xạ khoá tới giá trị.

Giá trị zero của map là nil. Map nil không có khoá nào, và cũng không thể thêm khoá vào.

Hàm make trả về một map thuộc kiểu bạn chỉ định, đã khởi tạo sẵn sàng để dùng.

package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m map[string]Vertex

func main() {
	m = make(map[string]Vertex)
	m["Bell Labs"] = Vertex{
		40.68433, -74.39967,
	}
	fmt.Println(m["Bell Labs"])
}

Chạy thử ví dụ này trong Tour of Go ↗

Map literal

Map literal giống struct literal, nhưng bắt buộc phải có khoá.

package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m = map[string]Vertex{
	"Bell Labs": Vertex{
		40.68433, -74.39967,
	},
	"Google": Vertex{
		37.42202, -122.08408,
	},
}

func main() {
	fmt.Println(m)
}

Chạy thử ví dụ này trong Tour of Go ↗

Map literal (tiếp)

Nếu kiểu ở mức trên cùng chỉ là một tên kiểu, bạn có thể lược bỏ nó ở các phần tử của literal.

package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m = map[string]Vertex{
	"Bell Labs": {40.68433, -74.39967},
	"Google":    {37.42202, -122.08408},
}

func main() {
	fmt.Println(m)
}

Chạy thử ví dụ này trong Tour of Go ↗

Thay đổi nội dung map

Thêm hoặc cập nhật một phần tử trong map m:

m[key] = elem

Lấy ra một phần tử:

elem = m[key]

Xoá một phần tử:

delete(m, key)

Kiểm tra một khoá có tồn tại hay không bằng phép gán hai giá trị:

elem, ok = m[key]

Nếu key có trong m thì ok là true. Nếu không, ok là false.

Nếu key không có trong map thì elem là giá trị zero của kiểu phần tử của map.

Lưu ý: Nếu elem hoặc ok chưa được khai báo, bạn có thể dùng dạng khai báo ngắn:

elem, ok := m[key]
package main

import "fmt"

func main() {
	m := make(map[string]int)

	m["Answer"] = 42
	fmt.Println("The value:", m["Answer"])

	m["Answer"] = 48
	fmt.Println("The value:", m["Answer"])

	delete(m, "Answer")
	fmt.Println("The value:", m["Answer"])

	v, ok := m["Answer"]
	fmt.Println("The value:", v, "Present?", ok)
}

Chạy thử ví dụ này trong Tour of Go ↗

Bài tập: Map

Hãy hiện thực hàm WordCount. Hàm này trả về một map đếm số lần xuất hiện của mỗi “từ” trong chuỗi s. Hàm wc.Test chạy một bộ test trên hàm bạn viết và in ra kết quả đạt hay không đạt.

Có thể bạn sẽ thấy strings.Fields hữu ích.

package main

import (
	"golang.org/x/tour/wc"
)

func WordCount(s string) map[string]int {
	return map[string]int{"x": 1}
}

func main() {
	wc.Test(WordCount)
}

Chạy thử ví dụ này trong Tour of Go ↗

Function value

Hàm cũng là giá trị. Chúng có thể được truyền đi như mọi giá trị khác.

Function value có thể dùng làm đối số của hàm và làm giá trị trả về.

package main

import (
	"fmt"
	"math"
)

func compute(fn func(float64, float64) float64) float64 {
	return fn(3, 4)
}

func main() {
	hypot := func(x, y float64) float64 {
		return math.Sqrt(x*x + y*y)
	}
	fmt.Println(hypot(5, 12))

	fmt.Println(compute(hypot))
	fmt.Println(compute(math.Pow))
}

Chạy thử ví dụ này trong Tour of Go ↗

Closure của hàm

Hàm trong Go có thể là closure. Closure là một function value tham chiếu tới các biến nằm ngoài thân hàm của nó. Hàm đó có thể đọc và gán vào những biến được tham chiếu; theo nghĩa này, hàm bị “gắn” với các biến đó.

Ví dụ, hàm adder trả về một closure. Mỗi closure gắn với một biến sum của riêng nó.

package main

import "fmt"

func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}
}

func main() {
	pos, neg := adder(), adder()
	for i := 0; i < 10; i++ {
		fmt.Println(
			pos(i),
			neg(-2*i),
		)
	}
}

Chạy thử ví dụ này trong Tour of Go ↗

Bài tập: closure Fibonacci

Giờ hãy nghịch một chút với hàm.

Hãy hiện thực hàm fibonacci sao cho nó trả về một hàm (một closure) lần lượt trả về các số Fibonacci (0, 1, 1, 2, 3, 5, …).

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
}

func main() {
	f := fibonacci()
	for i := 0; i < 10; i++ {
		fmt.Println(f())
	}
}

Chạy thử ví dụ này trong Tour of Go ↗

Chúc mừng!

Bạn đã hoàn thành bài học này!

Bạn có thể quay lại danh sách các mô-đun để chọn phần học tiếp theo, hoặc đi tiếp sang bài học kế tiếp.

Nội dung gốc: Go Documentation © The Go Authors, giấy phép CC BY 3.0. Bản dịch và ghi chú: Lê Văn Dũng.