Menu

Mảng trong Golang: độ dài cố định, sao chép khi gán, vs slice

Mảng trong Go có độ dài cố định thuộc về chính kiểu của nó, và được sao chép khi gán. Tìm hiểu cách khai báo, duyệt, so sánh và truyền mảng, cùng lý do phần lớn code Go dùng slice thay cho mảng.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Khai báo mảng

Kiểu mảng (array) được viết là [N]T: một độ dài, rồi đến kiểu phần tử. Độ dài được cố định lúc biên dịch và là một phần của kiểu.

Đầu ra:

[90 0 0 0 75] 5
Tue
[5]int [2 3 5 7 11]
["" "b" "" "d" ""]

Mảng luôn được khởi tạo đầy đủ. Phần tử nào bạn không gán sẽ mang giá trị zero của kiểu phần tử: 0, "", false, nil.

Chỉ số và giới hạn

Chỉ số chạy từ 0 đến len(a)-1. Một chỉ số hằng nằm ngoài phạm vi là lỗi biên dịch. Một chỉ số là biến nằm ngoài phạm vi sẽ gây panic lúc chạy.

var a [3]int
a[5] = 1 // compile error: invalid argument: index 5 out of bounds [0:3]

i := 5
a[i] = 1 // panic: runtime error: index out of range [5] with length 3

Duyệt mảng

for i, v := range a đi qua từng phần tử theo thứ tự. v là bản sao của phần tử, nên gán vào v không làm thay đổi mảng. Hãy ghi qua chỉ số.

Có thêm một điểm tinh tế chỉ riêng mảng mới có: range trên một giá trị mảng sẽ lấy bản sao của mảng ngay từ đầu. Nếu thân vòng lặp sửa các phần tử phía sau, v vẫn cho thấy giá trị cũ. Duyệt qua &a hoặc a[:] sẽ tránh được bản sao này.

Mảng là giá trị

Đây là điểm khác biệt chính so với mảng trong C, Java hay JavaScript. Gán một mảng sẽ sao chép mọi phần tử. Truyền mảng vào hàm cũng sao chép nó.

Đầu ra:

[1 2 3] [99 2 3]
[1 2 3]
[0 2 3]

Sao chép mảng nhỏ thì rẻ, mảng lớn thì tốn: truyền một [1_000_000]int theo giá trị sẽ sao chép tám megabyte. Hãy truyền con trỏ (*[N]T) hoặc slice (a[:]) khi mảng lớn hoặc khi hàm cần thay đổi nó. Truy cập chỉ số qua con trỏ mảng không cần viết *: a[0] dùng được trên một *[3]int.

So sánh mảng và dùng mảng làm khóa của map

Mảng gồm các phần tử so sánh được hỗ trợ == và !=. Hai mảng bằng nhau khi từng phần tử bằng nhau. Điều đó cũng khiến mảng hợp lệ để làm khóa của map, còn slice thì không.

Các mảng có độ dài khác nhau là các kiểu khác nhau và hoàn toàn không so sánh được: [3]int{} == [4]int{} không biên dịch được (mismatched types [3]int and [4]int).

Mảng nhiều chiều

Một mảng chứa các mảng cho bạn một lưới cố định. Toàn bộ lưới là một khối bộ nhớ liền mạch.

row[:] biến hàng [3]rune thành một slice []rune, kiểu mà string() chấp nhận.

Mảng so với slice

Mảng [N]TSlice []T
Độ dàicố định, là một phần của kiểuthay đổi qua append
Gán và truyềnsao chép mọi phần tửsao chép một header nhỏ, dùng chung phần tử
Giá trị zeroN phần tử zeronil, độ dài 0
==có, từng phần tử mộtchỉ khi so với nil
Khóa của mapcókhông
Cách dùng thường gặpdữ liệu kích thước cố định: hash, tọa độ, bufferhầu hết mọi trường hợp khác

Trên thực tế, bạn chủ yếu gặp mảng ở vài chỗ: sha256.Sum256 trả về một [32]byte, các bảng tra cứu có kích thước không bao giờ đổi, và làm vùng lưu trữ phía sau một slice. Cắt mảng bằng a[:] hoặc a[1:3] tạo ra một slice dùng chung bộ nhớ với mảng; trang slices giải thích việc dùng chung này nghĩa là gì.

Chuyển đổi giữa mảng và slice

Chuyển một slice thành mảng (Go 1.20) hoặc con trỏ mảng (Go 1.17) sẽ gây panic nếu slice ngắn hơn độ dài mảng: panic: runtime error: cannot convert slice with length 2 to array or pointer to array with length 3.

Lỗi thường gặp

  • Mong một hàm sửa được mảng truyền vào. Hàm chỉ nhận một bản sao. Hãy truyền *[N]T hoặc slice.
  • Dùng mảng ở chỗ cần slice. []int{1, 2} là slice, [2]int{1, 2} là mảng. Một hàm nhận []int sẽ không chấp nhận [2]int; hãy truyền a[:].
  • Mảng lớn trong vòng lặp range. for _, v := range bigArray sao chép toàn bộ mảng trước. Hãy duyệt qua &bigArray hoặc một slice.

Câu hỏi thường gặp

Khai báo mảng trong Go như thế nào?

Viết độ dài trong ngoặc vuông, trước kiểu phần tử: var a [5]int tạo năm số 0. Kèm giá trị: a := [3]string{"x", "y", "z"}. Dùng [...] để trình biên dịch tự đếm: a := [...]int{1, 2, 3} có kiểu [3]int.

Mảng và slice trong Go khác nhau thế nào?

Mảng có độ dài cố định thuộc về kiểu của nó ([3]int và [4]int là hai kiểu khác nhau) và được sao chép khi gán hoặc truyền vào hàm. Slice ([]int) là một khung nhìn vào một mảng nằm bên dưới, có độ dài thay đổi được qua append, và sao chép slice chỉ sao chép khung nhìn đó, không sao chép các phần tử. Phần lớn code Go dùng slice.

Lấy độ dài của mảng trong Go bằng cách nào?

Dùng hàm có sẵn len(a). Với mảng, đây là hằng số biết trước lúc biên dịch, nên bạn dùng được nó trong biểu thức hằng. cap(a) trả về cùng con số đó.

Có so sánh được hai mảng trong Go không?

Có, bằng == và !=, nếu kiểu phần tử so sánh được. Hai mảng bằng nhau khi mọi phần tử của chúng bằng nhau. Mảng cũng dùng được làm khóa của map. Slice thì không so sánh được bằng == (trừ khi so với nil); với slice, hãy dùng slices.Equal.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu