[Golang cơ bản] Kiểu dữ liệu mảng trong Golang

Post on: 2023-03-10 23:04:09 | in: Golang
Mảng (array) trong Golang là một cấu trúc dữ liệu dùng để lưu trữ một tập hợp các giá trị cùng kiểu dữ liệu và có thể được truy cập thông qua chỉ số của mỗi phần tử trong mảng.

Tổng quan

Giống như bất kỳ ngôn ngữ lập trình nào khác, golang cũng có cấu trúc dữ liệu mảng. Tuy nhiên, trong golang, mảng hoạt động khác một chút so với các ngôn ngữ khác và chúng ta còn có điều gọi là slice trong golang, tương tự như một tham chiếu đến một mảng. Trong bài viết này, chúng ta sẽ chỉ nghiên cứu về mảng.

Định nghĩa

Một mảng là một tập hợp liên tục các phần tử cùng kiểu dữ liệu. Nó là một chuỗi các phần tử được lưu trữ liên tục trong bộ nhớ theo thứ tự đã xác định.

Dưới đây là định dạng cho khai báo một mảng.

sample := [size_of_array]{type}{a1, a2... an}
  • size_of_array - số lượng phần tử trong mảng
  • - kiểu dữ liệu của từng phần tử trong mảng
  • a1, a2 ... an - là các phần tử thực tế trong mảng.

Trong golang, kích thước của mảng là một phần của kiểu dữ liệu của nó. Điều này có nghĩa là hai mảng có số phần tử khác nhau là hai kiểu dữ liệu khác nhau và một mảng không thể được gán cho mảng khác. Lỗi dưới đây sẽ được thông báo nếu chúng ta cố gắng gán hai mảng khác độ dài với nhau.

cannot use sample1 (type [1]int) as type [2]int in assignment

Mã lệnh là:

sample1 := [1]int{1}
sample2 := [2]int{1,2}


sample2 = sample1

Vì cùng một lý do, độ dài của mảng được xác định cố định trong quá trình tạo và không thể thay đổi sau này.

Khai báo một mảng

Cả số lượng phần tử và các phần tử thực tế đều là tùy chọn trong khai báo mảng.

Trong ví dụ dưới đây, chúng ta thấy 4 cách khai báo một mảng:

  • Chỉ định cả độ dài của mảng và các phần tử thực tế. Ví dụ:
[2]int{1, 2}
  • Chỉ có độ dài - Trong trường hợp này, tất cả các phần tử thực tế được điền bằng giá trị mặc định bằng 0 của kiểu dữ liệu đó. Ví dụ:
[2]int{}
  • Chỉ có các phần tử thực tế - Trong trường hợp này, độ dài của mảng sẽ bằng với số lượng phần tử thực tế. Ký hiệu '…' phải được sử dụng trong ngoặc vuông như thế này '[...]' khi không chỉ định độ dài. Ký hiệu này là một chỉ thị cho trình biên dịch để tính toán độ dài.
[...]int{2, 3}
  • Không có độ dài và các phần tử thực tế - Một mảng trống sẽ được tạo ra trong trường hợp này. Tương tự như trên, ký hiệu '…' cũng cần được sử dụng trong trường hợp này.
[...]int{}

Chúng ta hãy xem một ví dụ mã giải thích các điểm trên. Vui lòng lưu ý rằng chức năng tích hợp sẵn len() có thể được sử dụng để tính toán độ dài của một mảng. Trong chương trình dưới đây, chúng ta sử dụng hàm len() để tính toán độ dài của mảng.

package main

import "fmt"

func main() {
    //Both number of elements and actual elements
    sample1 := [2]int{1, 2}
    fmt.Printf("Sample1: Len: %d, %v\n", len(sample1), sample1)

    //Only actual elements
    sample2 := [...]int{2, 3}
    fmt.Printf("Sample2: Len: %d, %v\n", len(sample2), sample2)

    //Only number of elements
    sample3 := [2]int{}
    fmt.Printf("Sample3: Len: %d, %v\n", len(sample3), sample3)

    //Without both number of elements and actual elements
    sample4 := [...]int{}
    fmt.Printf("Sample4: Len: %d, %v\n", len(sample4), sample4)
}

Output

Sample1: Len: 2, [1 2]
Sample2: Len: 2, [2 3]
Sample3: Len: 2, [0 0]
Sample4: Len: 0, []

Chú ý trong ví dụ trên rằng đối với biến sample3, các phần tử thực tế được điền bằng giá trị mặc định của int là 0.

Nếu số phần tử thực tế được chỉ định ít hơn độ dài của mảng thì cũng không sao cả. Các phần tử còn lại sẽ được điền bằng giá trị mặc định của kiểu dữ liệu được chỉ định. Xem ví dụ dưới đây. Độ dài của mảng được chỉ định là 4 trong khi chỉ có 2 phần tử thực tế được khai báo. Do đó, hai phần tử còn lại được gán giá trị 0 là giá trị mặc định của một int.

package main

import "fmt"

func main() {
    sample := [4]int{5, 8}
    fmt.Printf("Sample: Len: %d, %v\n", len(sample), sample)
}

Output

Sample: Len: 4, [5 8 0 0]

Truy cập các phần tử của mảng

Do các phần tử của mảng được lưu trữ theo cách liên tục nhau, chúng ta có thể truy cập một phần tử của mảng bằng cách sử dụng một chỉ mục. Tương tự, các phần tử của mảng cũng có thể được gán giá trị bằng cách sử dụng chỉ mục. Truy cập chỉ mục nằm ngoài phạm vi của mảng sẽ gây ra lỗi biên dịch. Xem các ví dụ dưới đây minh họa cho các điểm này. Vị trí chỉ mục đầu tiên sẽ là zero và vị trí cuối cùng sẽ là (length_of_array-1).

package main

import "fmt"

func main() {
    sample := [2]string{"aa", "bb"}

    fmt.Println(sample[0])
    fmt.Println(sample[1])

    sample[0] = "xx"
    fmt.Println(sample)
    //sample[3] = "yy"
}

Output

aa
bb
[xx bb]

Nếu bỏ dấu chú thích cho dòng dưới đây.

sample[3] = "yy"

, nó sẽ gây ra lỗi biên dịch.

invalid array index 3 (out of bounds for 2-element array)

Mảng là giá trị trong Go

Mảng là kiểu giá trị trong Go. Vì vậy, tên biến mảng không phải là con trỏ đến phần tử đầu tiên, thực tế nó biểu thị cho toàn bộ mảng. Một bản sao của mảng sẽ được tạo ra khi:

  • Một biến mảng được gán cho một biến mảng khác.
  • Một biến mảng được chuyển làm đối số cho một hàm.

Hãy xem các điểm trên với ví dụ sau đây.

package main

import "fmt"

func main() {
    sample1 := [2]string{"a", "b"}
    fmt.Printf("Sample1 Before: %v\n", sample1)
    sample2 := sample1
    sample2[0] = "c"
    fmt.Printf("Sample1 After assignment: %v\n", sample1)
    fmt.Printf("Sample2: %v\n", sample2)
    test(sample1)
    fmt.Printf("Sample1 After Test Function Call: %v\n", sample1)
}
func test(sample [2]string) {
    sample[0] = "d"
    fmt.Printf("Sample in Test function: %v\n", sample)
}

Output

Sample1 Before: [a b]
Sample1 After assignment: [a b]
Sample2:
Sample in Test function: [d b]
Sample1 After Test Function Call: [a b]

Đúng như trên ví dụ,

  • Chúng ta đã gán sample1 cho sample2 và sau đó thay đổi giá trị index 0 của sample2 thành giá trị khác. Sau đó, khi chúng ta in sample1, chúng ta thấy rằng nó không thay đổi. Điều này là vì khi chúng ta gán sample1 vào sample2, một bản sao được tạo ra và thay đổi sample2 không ảnh hưởng đến sample1
  • Chúng ta đã truyền sample1 vào hàm test và sau đó lại thay đổi giá trị của nó tại index 0 trong hàm test. Sau đó, khi chúng ta in sample1, chúng ta thấy rằng nó không thay đổi. Lý do là tương tự, khi sample1 được truyền vào hàm test như một đối số thì một bản sao của sample1 được tạo ra.

Có nhiều cách khác nhau để lặp qua một mảng

Một mảng có thể được duyệt qua bằng cách:

  • Sử dụng vòng lặp for
  • Sử dụng vòng lặp for-range

Hãy xem một ví dụ mã cho cả hai trường hợp.

package main

import "fmt"

func main() {
    letters := [3]string{"a", "b", "c"}
    //Using for loop
    fmt.Println("Using for loop")
    len := len(letters)
    for i := 0; i < len; i++ {
        fmt.Println(letters[i])
    }
    //Using for-range operator
    fmt.Println("\nUsing for-range loop")
    for i, letter := range letters {
        fmt.Printf("%d %s\n", i, letter)
    }
}

Output

Using for loop
a
b
c


Using for-range loop
0 a
1 b
2 c

Mảng đa chiều- MultiDimensional Arrays

Dưới đây là định dạng để khai báo một mảng hai chiều.

sample := [x][y]{type}{{a11, a12 .. a1y},
                       {a21, a22 .. a2y},
                       {.. },
                       {ax1, ax2 .. axy}}    

trong đó:

  • x là số hàng
  • y là số cột
  • aij là một phần tử có vị trí ở hàng i và cột j
  • type là kiểu dữ liệu của các phần tử trong mảng

Cùng ý tưởng này cũng có thể mở rộng cho mảng ba chiều, mảng bốn chiều và cứ tiếp tục như vậy. Tất cả các quy tắc chúng ta đã thảo luận ở trên đều áp dụng cho các mảng đa chiều.

Hãy xem một ví dụ mã để hiểu rõ hơn:

package main

import "fmt"

func main() {
    sample := [2][3]int{{1, 2, 3}, {4, 5, 6}}
    fmt.Println("First Run")
    for _, row := range sample {
        for _, val := range row {
            fmt.Println(val)
        }
    }

    sample[0][0] = 6
    sample[1][2] = 1
    fmt.Println("\nSecond Run")
    for _, row := range sample {
        for _, val := range row {
            fmt.Println(val)
        }
    }
}

Output

First Run
1
2
3
4
5
6


Second Run
6
2
3
4
5
1

Đúng rồi, ví dụ trên ta sử dụng chỉ số để truy cập phần tử của mảng hai chiều trong cả hai chiều đầu tiên.

sample[0][0] = 6

Chú ý cách duyệt mảng hai chiều ở ví dụ trên. Chúng ta cần sử dụng nested range. Range đầu tiên duyệt qua các mảng con trong mảng chính. Range thứ hai duyệt qua từng mảng con trong đó.

Kết luận

Đây là tất cả về mảng trong Golang. Hy vọng bạn đã thích bài viết này. Vui lòng chia sẻ phản hồi/cải tiến/sai sót trong phần bình luận.

Tag: golang cơ bản go