Menu
CoddyTech

Введение в Generics

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

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

Представь, что нужно найти минимальное значение в срезе. Без обобщённых типов пришлось бы написать отдельные функции для каждого типа:

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
}

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

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}) с одной и той же функцией. Компилятор проверяет типы во время компиляции, поэтому вы получаете безопасность без дополнительных затрат во время выполнения.

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

challenge icon

Задание

Легко

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

Вы распределите код по двум файлам:

  • analyzer.go: Определите обобщённую функцию анализа.

    Создайте обобщённую функцию Max[T int | float64](values []T) T, которая находит и возвращает максимальное значение в срезе. Функция должна работать и с типом int, и с типом float64 благодаря параметру типа T.

    Также создайте обобщённую функцию Sum[T int | float64](values []T) T, которая вычисляет и возвращает сумму всех значений в срезе.

  • main.go: Считайте входные данные и продемонстрируйте работу обобщённых функций с разными типами.

    Считайте индикатор типа (int или float), затем считайте количество значений и столько же самих значений. Разберите значения в соответствии с типом, вызовите и Max, и Sum с подходящим типом и выведите результаты.

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

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

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

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

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

  • Строка 1: индикатор типа (int или float)
  • Строка 2: количество значений (целое число)
  • Следующие строки: по одному значению в каждой строке

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

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

Обратите внимание, как одни и те же обобщённые функции работают и с целыми числами, и с числами с плавающей точкой: параметр типа [T int | float64] позволяет компилятору создавать типобезопасный код для каждого использования, а вам достаточно написать логику всего один раз.

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

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