Menu
Coddy logo textTech

Введение в Generics

Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 67 из 107.

До Go 1.18 написание повторно используемого кода для разных типов означало либо дублирование функций, либо использование пустого интерфейса any с проверками типов. Generics решают эту проблему, позволяя писать функции и типы, которые работают с несколькими типами, сохраняя при этом полную типобезопасность.

Рассмотрим поиск минимального значения в slice. Без generic потребовались бы отдельные функции для каждого типа:

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
}

С помощью обобщений вы один раз записываете логику, используя type parameter в квадратных скобках:

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] объявляет параметр типа T, который может быть либо int, либо float64. Теперь вы можете вызвать Min([]int{3, 1, 2}) или Min([]float64{3.5, 1.2}) с помощью одной и той же функции. Компилятор проверяет типы во время компиляции, поэтому вы получаете безопасность без дополнительных затрат во время выполнения.

В следующих уроках мы подробно рассмотрим параметры типов, ограничения и generic-структуры.

challenge icon

Задание

Легко

Давайте создадим анализатор результатов, который работает как с целыми, так и с числами с плавающей точкой, используя generic! Вы создадите одну generic function, которая может находить maximum значение в slice, устраняя необходимость в дублировании кода для разных числовых типов.

Вы организуете код в двух файлах:

  • analyzer.go: Определите свою generic function для анализа.

    Создайте generic function Max[T int | float64](values []T) T, которая находит и возвращает maximum значение в slice. Function должна работать как с типом int, так и с типом float64 посредством type parameter T.

    Также создайте generic function Sum[T int | float64](values []T) T, которая calculates и returns сумму всех values в slice.

  • main.go: Считайте входные данные и продемонстрируйте свои generic functions с разными типами.

    Считайте indicator типа (int или float), затем считайте count, за которым следует соответствующее количество values. Разберите values в соответствии с типом, вызовите обе функции Max и Sum с подходящим типом и выведите results.

    Для целочисленного ввода выведите:

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

    Для ввода с плавающей точкой выведите значение с одним десятичным знаком:

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

Будут предоставлены следующие входные данные:

  • Строка 1: Type indicator (int или float)
  • Строка 2: Count values (integer)
  • Следующие строки: По одному значению в каждой строке

Например, для следующих данных:

int
4
15
8
23
11

Ваш вывод должен быть таким:

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

А для следующих данных:

float
3
4.5
9.2
6.8

Ваш вывод должен быть таким:

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

Обратите внимание, как одни и те же generic functions обрабатывают и целочисленные данные, и данные с плавающей точкой: type parameter [T int | float64] позволяет компилятору генерировать type-safe код для каждого использования, а вы при этом пишете логику только один раз.

Попробуйте сами

package main

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

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

	// Прочитать индикатор типа
	typeStr, _ := reader.ReadString('\n')
	typeStr = strings.TrimSpace(typeStr)

	// Прочитать количество
	countStr, _ := reader.ReadString('\n')
	count, _ := strconv.Atoi(strings.TrimSpace(countStr))

	if typeStr == "int" {
		// Прочитать целочисленные значения
		values := make([]int, count)
		for i := 0; i < count; i++ {
			line, _ := reader.ReadString('\n')
			values[i], _ = strconv.Atoi(strings.TrimSpace(line))
		}

		// TODO: Вызвать Max и Sum со срезом целых чисел
		// TODO: Вывести результаты в формате "Max (int): [value]" и "Sum (int): [value]"

	} else if typeStr == "float" {
		// Прочитать значения с плавающей точкой
		values := make([]float64, count)
		for i := 0; i < count; i++ {
			line, _ := reader.ReadString('\n')
			values[i], _ = strconv.ParseFloat(strings.TrimSpace(line), 64)
		}

		// TODO: Вызвать Max и Sum со срезом float64
		// TODO: Вывести результаты с одним знаком после запятой в формате "Max (float): [value]" и "Sum (float): [value]"
	}
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Go