Menu
CoddyTech

Giới thiệu về Generics

Thuộc phần Lập trình hướng đối tượng trong hành trình GO của Coddy. Bài học 67 trên 107.

Trước Go 1.18, để viết code có thể tái sử dụng cho nhiều kiểu dữ liệu khác nhau, bạn phải sao chép hàm hoặc dùng giao diện rỗng any cùng với các phép khẳng định kiểu. Generics giải quyết vấn đề này bằng cách cho phép bạn viết hàm và kiểu dữ liệu hoạt động với nhiều kiểu dữ liệu mà vẫn đảm bảo an toàn kiểu hoàn toàn.

Hãy thử tìm giá trị nhỏ nhất trong một lát cắt. Nếu không dùng kiểu tổng quát, bạn sẽ cần các hàm riêng cho từng kiểu dữ liệu:

func MinInt(values []int) int {
    min := values[0]
    for _, v := range values {
        if v < min {
            min = v
        }
    }
    return min
}

func MinFloat64(values []float64) float64 {
    min := values[0]
    for _, v := range values {
        if v < min {
            min = v
        }
    }
    return min
}

Với generic, bạn chỉ cần viết logic một lần bằng cách dùng tham số kiểu trong dấu ngoặc vuông:

func Min[T int | float64](values []T) T {
    min := values[0]
    for _, v := range values {
        if v < min {
            min = v
        }
    }
    return min
}

[T int | float64] khai báo tham số kiểu T, có thể là int hoặc float64. Giờ đây, bạn có thể gọi Min([]int{3, 1, 2}) hoặc Min([]float64{3.5, 1.2}) bằng cùng một hàm. Trình biên dịch kiểm tra kiểu dữ liệu tại thời điểm biên dịch, nên bạn có được tính an toàn mà không tốn thêm chi phí khi chạy chương trình.

Trong các bài học tiếp theo, chúng ta sẽ tìm hiểu chi tiết về tham số kiểu, các ràng buộc và struct generic.

challenge icon

Thử thách

Dễ

Hãy xây dựng một chương trình phân tích điểm số có thể xử lý cả điểm số nguyên lẫn số thực bằng kiểu tổng quát! Bạn sẽ tạo một hàm tổng quát duy nhất để tìm giá trị lớn nhất trong một lát cắt, tránh phải viết mã trùng lặp cho các kiểu số khác nhau.

Bạn sẽ sắp xếp code trong hai tệp:

  • analyzer.go: Định nghĩa hàm phân tích tổng quát.

    Tạo hàm tổng quát Max[T int | float64](values []T) T để tìm và trả về giá trị lớn nhất trong một lát cắt. Hàm này nên xử lý được cả kiểu int lẫn float64 thông qua tham số kiểu T.

    Đồng thời, tạo hàm tổng quát Sum[T int | float64](values []T) T để tính và trả về tổng tất cả giá trị trong một lát cắt.

  • main.go: Đọc đầu vào và minh họa các hàm tổng quát với những kiểu dữ liệu khác nhau.

    Đọc chỉ báo kiểu dữ liệu (int hoặc float), sau đó đọc số lượng, rồi đọc số giá trị tương ứng. Phân tích các giá trị theo kiểu dữ liệu, gọi cả Max lẫn Sum với kiểu dữ liệu thích hợp, rồi in kết quả.

    Với đầu vào là số nguyên, hãy in:

    Max (int): [value]
    Sum (int): [value]

    Với đầu vào là số thực, hãy in kết quả với một chữ số thập phân:

    Max (float): [value]
    Sum (float): [value]

Đầu vào sẽ có dạng sau:

  • Dòng 1: Chỉ báo kiểu dữ liệu (int hoặc float)
  • Dòng 2: Số lượng giá trị (số nguyên)
  • Các dòng tiếp theo: Mỗi dòng có một giá trị

Ví dụ, với đầu vào:

int
4
15
8
23
11

Kết quả đầu ra sẽ là:

Max (int): 23
Sum (int): 57

Và với đầu vào:

float
3
4.5
9.2
6.8

Kết quả đầu ra sẽ là:

Max (float): 9.2
Sum (float): 20.5

Lưu ý rằng cùng một hàm tổng quát có thể xử lý cả dữ liệu số nguyên lẫn số thực: tham số kiểu [T int | float64] cho phép trình biên dịch tạo code an toàn về kiểu dữ liệu cho mỗi lần sử dụng, trong khi bạn chỉ cần viết logic một lần.

Tự làm thử

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Đọc giá trị xác định kiểu dữ liệu
	typeStr, _ := reader.ReadString('\n')
	typeStr = strings.TrimSpace(typeStr)

	// Đọc số lượng
	countStr, _ := reader.ReadString('\n')
	count, _ := strconv.Atoi(strings.TrimSpace(countStr))

	if typeStr == "int" {
		// Đọc các giá trị số nguyên
		values := make([]int, count)
		for i := 0; i < count; i++ {
			line, _ := reader.ReadString('\n')
			values[i], _ = strconv.Atoi(strings.TrimSpace(line))
		}

		// TODO: Gọi Max và Sum với lát cắt số nguyên
		// TODO: In kết quả theo định dạng "Max (int): [value]" và "Sum (int): [value]"

	} else if typeStr == "float" {
		// Đọc các giá trị số thực
		values := make([]float64, count)
		for i := 0; i < count; i++ {
			line, _ := reader.ReadString('\n')
			values[i], _ = strconv.ParseFloat(strings.TrimSpace(line), 64)
		}

		// TODO: Gọi Max và Sum với lát cắt float64
		// TODO: In kết quả với một chữ số thập phân theo định dạng "Max (float): [value]" và "Sum (float): [value]"
	}
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Go online