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.