Menu
Coddy logo textTech

Введение в 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 с соответствующим типом и выведите результаты.

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

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

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

    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] позволяет компилятору генерировать типобезопасный код для каждого случая использования, в то время как вы пишете логику только один раз.

Шпаргалка

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

Параметры типов объявляются в квадратных скобках и указывают, какие типы может принимать универсальная функция:

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})           // Работает с int
Min([]float64{3.5, 1.2})      // Работает с 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 со срезом целых чисел (int)
		// 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Проверьте себя

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

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