Khóa học go

Generics trong Go Lang

0 phút đọc

Generics là một tính năng quan trọng trong lập trình, cho phép bạn viết mã linh hoạt và tái sử dụng bằng cách làm việc với các kiểu dữ liệu khác nhau mà không cần phải viết lại mã cho từng kiểu cụ thể. Trong Go, generics đã được giới thiệu từ phiên bản 1.18, mang lại khả năng viết các hàm và kiểu dữ liệu tổng quát hơn. Bài viết này sẽ cung cấp một cái nhìn tổng quan chi tiết về generics trong Go, bao gồm cú pháp, cách sử dụng, và các ví dụ minh họa cụ thể.

Khái Niệm Generics

Generics trong Go cho phép bạn viết các hàm, kiểu dữ liệu, và giao diện (interface) có thể làm việc với nhiều kiểu dữ liệu khác nhau. Điều này giúp giảm thiểu việc viết mã lặp lại và tăng tính linh hoạt của mã nguồn.

Cú Pháp Generics

Khai Báo Hàm Generic

Để khai báo một hàm generic, bạn sử dụng cú pháp sau:

go Copy
func TênHàm[T any](thamSố T) T {
    // Thân hàm
}

Trong đó:

  • T là tham số kiểu (type parameter) và any là ràng buộc kiểu (type constraint) cho phép bất kỳ kiểu dữ liệu nào.

Ví Dụ:

go Copy
package main

import "fmt"

// Hàm generic để hoán đổi hai giá trị
func Swap[T any](a, b *T) {
    *a, *b = *b, *a
}

func main() {
    x, y := 1, 2
    Swap(&x, &y)
    fmt.Println(x, y) // Output: 2 1

    a, b := "hello", "world"
    Swap(&a, &b)
    fmt.Println(a, b) // Output: world hello
}

Khai Báo Kiểu Dữ Liệu Generic

Bạn cũng có thể khai báo các kiểu dữ liệu generic bằng cách sử dụng cú pháp tương tự.

Ví Dụ:

go Copy
package main

import "fmt"

// Khai báo kiểu dữ liệu generic
type Pair[T any] struct {
    First, Second T
}

func main() {
    intPair := Pair[int]{First: 1, Second: 2}
    fmt.Println(intPair) // Output: {1 2}

    stringPair := Pair[string]{First: "hello", Second: "world"}
    fmt.Println(stringPair) // Output: {hello world}
}

Ràng Buộc Kiểu (Type Constraints)

Ràng buộc kiểu cho phép bạn giới hạn các kiểu dữ liệu có thể được sử dụng với một hàm hoặc kiểu generic. Go cung cấp một số ràng buộc kiểu tích hợp sẵn như comparableOrdered.

Ví Dụ:

go Copy
package main

import (
    "fmt"
    "golang.org/x/exp/constraints"
)

// Hàm generic với ràng buộc kiểu Ordered
func Min[T constraints.Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}

func main() {
    fmt.Println(Min(3, 4))       // Output: 3
    fmt.Println(Min("a", "b"))   // Output: a
}

Sử Dụng Generics với Các Cấu Trúc Dữ Liệu

Generics có thể được sử dụng với các cấu trúc dữ liệu như slices, maps, và channels để tạo ra các hàm và kiểu dữ liệu linh hoạt hơn.

Ví Dụ:

go Copy
package main

import "fmt"

// Hàm generic để lấy các khóa của một map
func MapKeys[K comparable, V any](m map[K]V) []K {
    keys := make([]K, 0, len(m))
    for k := range m {
        keys = append(keys, k)
    }
    return keys
}

func main() {
    m := map[string]int{"a": 1, "b": 2, "c": 3}
    keys := MapKeys(m)
    fmt.Println(keys) // Output: [a b c]
}

Sử Dụng Generics với Giao Diện (Interfaces)

Generics có thể được sử dụng với giao diện để tạo ra các hàm và kiểu dữ liệu linh hoạt hơn.

Ví Dụ:

go Copy
package main

import "fmt"

// Định nghĩa giao diện Describer
type Describer interface {
    Describe() string
}

// Định nghĩa kiểu dữ liệu generic với giao diện Describer
type Container[T Describer] struct {
    Value T
}

func (c Container[T]) Describe() string {
    return c.Value.Describe()
}

// Định nghĩa kiểu dữ liệu Person triển khai giao diện Describer
type Person struct {
    Name string
}

func (p Person) Describe() string {
    return "Person: " + p.Name
}

func main() {
    p := Person{Name: "Alice"}
    c := Container[Person]{Value: p}
    fmt.Println(c.Describe()) // Output: Person: Alice
}

Sử Dụng Generics với Các Hàm Tiện Ích

Generics có thể được sử dụng để tạo ra các hàm tiện ích linh hoạt hơn.

Ví Dụ:

go Copy
package main

import "fmt"

// Hàm generic để lọc các phần tử trong một slice
func Filter[T any](s []T, f func(T) bool) []T {
    var result []T
    for _, v := range s {
        if f(v) {
            result = append(result, v)
        }
    }
    return result
}

func main() {
    nums := []int{1, 2, 3, 4, 5}
    evens := Filter(nums, func(n int) bool {
        return n%2 == 0
    })
    fmt.Println(evens) // Output: [2 4]
}

Lợi Ích của Generics

  1. Tái Sử Dụng Mã: Generics cho phép bạn viết mã một lần và sử dụng lại cho nhiều kiểu dữ liệu khác nhau.
  2. Giảm Thiểu Lỗi: Bằng cách sử dụng generics, bạn có thể giảm thiểu lỗi do việc sao chép và dán mã.
  3. Tăng Tính Linh Hoạt: Generics cho phép bạn viết các hàm và kiểu dữ liệu linh hoạt hơn, có thể làm việc với nhiều kiểu dữ liệu khác nhau.

Hạn Chế của Generics

  1. Hiệu Suất: Sử dụng generics có thể ảnh hưởng đến hiệu suất do việc kiểm tra kiểu dữ liệu tại thời điểm biên dịch.
  2. Độ Phức Tạp: Generics có thể làm tăng độ phức tạp của mã, đặc biệt là đối với những người mới bắt đầu.
  3. Thông Báo Lỗi: Thông báo lỗi liên quan đến generics có thể khó hiểu và khó gỡ lỗi.

Kết Luận

Generics là một tính năng mạnh mẽ trong Go, cho phép bạn viết mã linh hoạt và tái sử dụng bằng cách làm việc với các kiểu dữ liệu khác nhau. Bài viết này đã cung cấp một cái nhìn tổng quan chi tiết về generics trong Go, bao gồm cú pháp, cách sử dụng, và các ví dụ minh họa cụ thể. Hiểu rõ về generics sẽ giúp bạn viết mã Go hiệu quả và dễ bảo trì hơn.

Avatar
Được viết bởi

Admin Team

Gợi ý câu hỏi phỏng vấn

Không có dữ liệu

Không có dữ liệu

Gợi ý bài viết
Không có dữ liệu

Không có dữ liệu

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào