- vừa được xem lúc

Phần 4: Khám phá golang - Pointer, Structs, Slices, và Maps

0 0 16

Người đăng: Công Lâm

Theo Viblo Asia

Giới thiệu

Ở bài này mình sẽ giới thiệu những cấu trúc biến khác được phát triển từ các loại biến cơ bản và sẽ được sử dụng rất nhiều ở các bài toán thực tế. Bài này sẽ tương đối dài nên mình không dài dòng nữa, bắt đầu thôi 😄

Nội dung

Pointer

Đây là khái niệm mà hẳn các bạn từng học C/C++ đã biết và có thể sẽ khá "lú" về nó 😄 Vậy nên mình sẽ cố gắng trình bày một cách gọn gàng dễ hiểu nhất nhé.
Hiểu một các đơn giản thì con trỏ cũng là một loại biến nhưng đặc biệt ở chỗ nó được dùng để lưu trữ địa chỉ của một biến khác trong bộ nhớ RAM chứ không lưu trữ một giá trị cụ thể nào cả.
Hãy giả sử bạn đang xây dựng một nhà, và bạn có một bản vẽ hướng dẫn vị trí của mỗi phòng. Con trỏ giống như một mũi bút, chỉ vào vị trí cụ thể trên bản vẽ.

Khai báo con trỏ

  • Kiểu khai báo con trỏ nhưng không gán địa chỉ nào cho nó, lúc này giá trị default của nó là nil. Ví dụ:

    func main() { var p *int fmt.Println("p = ", p) // Output: p = <nil>
    }
    

    Biến p được khai báo là một con trỏ nhưng chưa được gán giá trị nào nên giá trị default của nó là nil.

  • Kiểu khai báo và gán địa chỉ, toán tử & được sử dụng để lấy địa chỉ của một biến. Ví dụ:

    var i = 10 // khai báo biến i có giá trị bằng 10
    var p = &i // gán địa chỉ biến i cho p kiểu *int, lúc này p là con trỏ lưu địa chỉ biến i
    
  • Toán tử * sẽ chỉ ra giá trị của cái địa chỉ lưu trên con trỏ.

    fmt.Println(*p) // đọc giá trị của i thông qua con trỏ p
    *p = 21 // thiết lập giá trị của i thông qua con trỏ p
    

    Ví dụ tổng quan:

    package main import "fmt" func main() { var a = 10 // B1: Gán giá trị biến a = 10 var p = &a // B2: gián địa chỉ biến a cho p kiểu *int => p là con trỏ lưu địa chỉ biến a fmt.Println("a = ", a) // Giá trị của biến a => 10 fmt.Println("p = ", p) // Địa chỉ của biến a => 0xc00001d0d0 fmt.Println("*p = ", *p) // Hiển thị giá trị lưu tại địa chỉ p, vì biến a lưu trữ trên địa chỉ này nên *p hiển thị giá trị của biến a => 10 *p = 20 // Vì giá trị lưu ở địa chỉ 0xc00001d0d0 đã thay đổi từ 10 thành 20 nên lúc này biến a có giá trị 20 => 20 fmt.Println("a (after) = ", a) }
    

    Output:

     a = 10 p = 0xc00001d0d0 *p = 20 a (after) = 20
    

Structs

  • Struct là một tập hợp các trường.

    package main import "fmt" type Vertex struct { X int Y int
    } func main() { fmt.Println(Vertex{1, 2}) // Output: {1 2}
    }
    
  • Các trường được truy cập bằng cách sử dụ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 // Thay đổi giá trị của X trong struct Vertex fmt.Println(v.X) // Output: 4
    }
    
  • Pointers to structs - Các trường có thể được truy cập thông qua một struct pointer.

    package main import "fmt" type Vertex struct { X int Y int
    } func main() { v := Vertex{1, 2} p := &v p.X = 3 fmt.Println(v) // Output: {3 2}
    }
    

    Để truy cập trường X của struct Vertex khi chúng ta có một struct pointer là p, chúng ta có thể viết (*p).X. Tuy nhiên nhìn nó khác cồng kềnh, vì thế nên Go cho phép chúng ta viết p.X là được.

    Ví dụ mở rộng:

     package main import "fmt" type Vertex struct { X, Y int } var ( v1 = Vertex{1, 2} // v1 có type là struct Vertex v2 = Vertex{X: 1} // Chỉ gán cho X, ngầm gán giá trị defaul Y:0 v3 = Vertex{} // X:0 và Y:0 p = &Vertex{1, 2} // có type là *Vertex ) func main() { fmt.Println(v1, p, v2, v3) // Output: {1 2} &{1 2} {1 0} {0 0} }
    

Arrays

  • Mảng trong Go được khai báo như sau: [n]T => Mảng gồm n giá trị của kiểu T
    Ví dụ: var a [10]int => khai báo một biến a là một mảng có 10 số nguyên.
  • Độ dài của mảng là một phần trong khai báo, vì thế nên mảng không thể thay đổi được kích thước.
    package main import "fmt" func main() { var a [2]string a[0] = "Hello" a[1] = "Golang" fmt.Println(a[0], a[1]) fmt.Println(a) primes := [6]int{2, 3, 5, 7, 11, 13} fmt.Println(primes)
    }
    
    Output:
     Hello Golang [Hello Golang] [2 3 5 7 11 13]
    

Slices

  • Một mảng có kích thước cố định. VÌ vậy slice ra đời để mang lại sự linh hoạt hơn về kích thước. Trong thực tế thì slice được sử dụng nhiều hơn so với mảng.

  • Khai báo slice: []T => một slice với các phần tử có kiểu T . Một slice được hình thành bằng cách chỉ định hai chỉ số, một giới hạn thấp và cao, cách nhau bởi một dấu hai chấm: a[thấp : cao]

    package main import "fmt" func main() { primes := [6]int{2, 3, 5, 7, 11, 13} var s []int = primes[1:4] fmt.Println(s) // Output: [3 5 7]
    }
    
  • Thay đổi các phần tử trong slice, khi hay đổi các phần tử của một slice thì sẽ sửa đổi các phần tử tương ứng của mảng cơ sở.

    package main import "fmt" func main() { names := [4]string{ "John", "Paul", "George", "Ringo", } fmt.Println(names) // [John Paul George Ringo] a := names[0:2] // [John Paul] b := names[1:3] // [Paul George] fmt.Println(a, b) b[0] = "XXX" fmt.Println(a, b) [John XXX] [XXX George] fmt.Println(names) [John XXX George Ringo] => slice names bị thay đổi theo slice b }
    
  • Slice literals: Tương tự như cú pháp của mảng nhưng không có chiều dài được chỉ định.

     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) }
    

    Output:

     [2 3 5 7 11 13] [true false true true false true] [{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]
    
  • Slice default: Khi "cắt lát" một slice, bạn có thể bỏ qua giới hạn thấp hoặc giới hạn cao. Giá trị default của giới hạn thấp là 0, cao là đội dài của slice.
    Ví dụ ta có array: var a [10]int Các biểu thức slice dưới đây là tương đương nhau:

    a[0:10]
    a[:10]
    a[0:]
    a[:]
    

    Ví dụ "cắt lát" slice:

    package main import "fmt" func main() { s := []int{2, 3, 5, 7, 11, 13} s = s[1:4] fmt.Println(s) // [3 5 7] s = s[:2] fmt.Println(s) // [3 5] s = s[1:] fmt.Println(s) // [5]
    }
    
  • Slice lengthcapacity: Một slice luôn có 2 thành phần là độ dài(length) và sức chứa(capacity):

    • Độ dài của một lát là số phần tử mà nó chứa.
    • Sức chứa của một lát là số phần tử trong mảng cơ sở, tính từ phần tử đầu tiên trong lát.
    • Độ dài và sức chứa của một lát s có thể được lấy bằng cách sử dụng các biểu thức len(s) và cap(s)
    • Bạn có thể mở rộng độ dài của một slice bằng cách cắt lại (re-slice) nó, miễn là nó có đủ sức chứa.
    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)
    }
    

    Ouput:

    len=6 cap=6 [2 3 5 7 11 13]
    len=0 cap=6 []
    len=4 cap=6 [2 3 5 7]
    len=2 cap=4 [5 7]
    
  • Nil slice: Giá trị default của một slice là nil, độ dài = 0 và sức chứa = 0

  • Tạo slice bằng make: Slice có thể được tạo bằng việc dùng built-in make function, đây là cách bạn tạo các mảng có kích thước động.

    a := make([]int, 5) // len(a)=5 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
    
  • Loop slice: Có thể dùng for range để loop slice, index sẽ được đánh từ 0 đến độ dài của slice

    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 x %d = %d\n", i, v) }
    }
    

    Output:

    2 x 0 = 1
    2 x 1 = 2
    2 x 2 = 4
    2 x 3 = 8
    2 x 4 = 16
    2 x 5 = 32
    2 x 6 = 64
    2 x 7 = 128
    

Maps

  • Map là một kiểu dữ liệu dạng bản đồ (associative array) được sử dụng để ánh xạ các khóa (keys) tới các giá trị (values). Mỗi khóa trong một bản đồ phải là duy nhất và không thể thay đổi.
  • Cú pháp khai báo map: var myMap map[KeyType]ValueType
    • KeyType: Kiểu dữ liệu của các khóa
    • ValueType: Kiểu dữ liệu của giá trị.
      Ví dụ: ar ages map[string]int // Một map có key là chuỗi và value là số nguyên
  • Một map cũng có thể được khởi tạo bằng hàm make, nhưng việc này thường không cần thiết nếu bạn chỉ muốn khởi tạo một map rỗng.
    ages := make(map[string]int)
  • Thêm và truy cập phần tử:
    • Để thêm một phần tử mới vào map, bạn có thể sử dụng cú pháp:
    ages["Alice"] = 30
    
    • Để truy cập một giá trị dựa trên một khóa, bạn có thể sử dụng cú pháp:
    fmt.Println("Tuổi của Alice là:", ages["Alice"])
    
  • Kiểm tra sự tồn tại của key: Bạn có thể kiểm tra xem một khóa có tồn tại trong map hay không bằng cách sử dụng hai giá trị trả về.
    age, ok := ages["Bob"]
    if ok { fmt.Println("Tuổi của Bob là:", age)
    } else { fmt.Println("Không tìm thấy tuổi của Bob")
    }
    
  • Xóa phần tử: Để xóa một phần tử khỏi map, bạn có thể sử dụng hàm delete()
    delete(ages, "Alice")
    
    Một ví dụ tổng quan:
    package main import "fmt" func main() { // Khởi tạo một map ages := make(map[string]int) // Thêm các phần tử vào map ages["Alice"] = 30 ages["Bob"] = 35 // Truy cập và in ra giá trị của một phần tử fmt.Println("Tuổi của Alice là:", ages["Alice"]) // Kiểm tra sự tồn tại của một khóa age, ok := ages["Bob"] if ok { fmt.Println("Tuổi của Bob là:", age) } else { fmt.Println("Không tìm thấy tuổi của Bob") } // Xóa một phần tử khỏi map delete(ages, "Alice") // In ra chiều dài của map fmt.Println("Số lượng phần tử trong map:", len(ages))
    }
    
    Output:
    Tuổi của Alice là: 30
    Tuổi của Bob là: 35
    Số lượng phần tử trong map: 1
    

Kết bài

Kết thúc một bài khá dài 😁
Trong bài thứ 4 này mình đã giới thiệu với các bạn về các types: pointer, structs, slices, and maps, chúng ta sẽ sử dụng rất rất nhiều những type này trong dự án thực tế đó 😄 Bài tiếp theo mình sẽ giới thiệu về Methods vaf interfaces trong Golang. Nếu có thắc mắc hoặc phát hiện bài viết có thiếu sót gì, hãy comment cho mình biết để mình kịp thời chỉnh sửa cũng như cùng thảo luận về thắc mắc đó nhé 😉 Hi vọng những kiến thức này sẽ giúp ích được các bạn trong hành trình chinh phục Golang. Hẹn gặp lại ở những bài viết tiếp theo trong series Golang Essentials: Nền Tảng và Kiến Thức Cơ Bản. See ya!

Tham khảo: https://go.dev/tour/moretypes

Bình luận

Bài viết tương tự

- vừa được xem lúc

gRPC - Nó là gì và có nên sử dụng hay không?

Nhân một ngày rảnh rỗi, mình ngồi đọc lại RPC cũng như gRPC viết lại để nhớ lâu hơn. Vấn đề là gì và tại sao cần nó .

0 0 131

- vừa được xem lúc

Embedded Template in Go

Getting Start. Part of developing a web application usually revolves around working with HTML as user interface.

0 0 56

- vừa được xem lúc

Tạo Resful API đơn giản với Echo framework và MySQL

1. Giới thiệu.

0 0 60

- vừa được xem lúc

Sử dụng goquery trong golang để crawler thông tin các website Việt Nam bị deface trên mirror-h.org

. Trong bài viết này, mình sẽ cùng mọi người khám phá một package thu thập dữ liệu có tên là goquery của golang. Mục tiêu chính của chương trình crawler này sẽ là lấy thông tin các website Việt Nam bị deface (là tấn công, phá hoại website, làm thay đổi giao diện hiển thị của một trang web, khi người

0 0 237

- vừa được xem lúc

Tạo ứng dụng craw dữ liệu bing với Golang, Mysql driver

Chào mọi người . Lâu lâu ta lại gặp nhau 1 lần, để tiếp tục series chia sẻ kiến thức về tech, hôm nay mình sẽ tìm hiểu và chia sẻ về 1 ngôn ngữ đang khá hot trong cộng đồng IT đó là Golang.

0 0 75

- vừa được xem lúc

Golang: Rest api and routing using MUX

Routing with MUX. Let's create a simple CRUD api for a blog site. # All . GET articles/ .

0 0 54