Menu
Coddy logo textTech

Передача по значению и по ссылке

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

Go — это язык с передачей по значению. Когда вы передаете переменную в функцию, Go создает копию этого значения. Это означает, что изменения внутри функции не влияют на исходную переменную.

func double(n int) {
    n = n * 2
}

func main() {
    x := 10
    double(x)
    fmt.Println(x)  // 10 (без изменений)
}

То же самое относится и к структурам. Передача структуры в функцию копирует все её поля, поэтому изменения внутри функции будут потеряны.

type Person struct {
    Name string
    Age  int
}

func birthday(p Person) {
    p.Age++
}

func main() {
    alice := Person{Name: "Alice", Age: 30}
    birthday(alice)
    fmt.Println(alice.Age)  // 30 (не изменилось)
}

Чтобы изменить исходное значение, передайте вместо него указатель. Сам указатель копируется, но он по-прежнему указывает на тот же адрес памяти, что позволяет вам изменять исходные данные.

func birthday(p *Person) {
    p.Age++
}

func main() {
    alice := Person{Name: "Alice", Age: 30}
    birthday(&alice)
    fmt.Println(alice.Age)  // 31 (изменено!)
}

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

challenge icon

Задание

Легко

Давайте создадим систему корзины покупок, которая демонстрирует разницу между передачей структур по значению и по указателю. Вы воочию увидите, как только параметры-указатели могут изменять исходные данные.

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

  • cart.go: Определите структуру CartItem с полями Name (string), Price (float64) и Quantity (int). Создайте две функции, которые пытаются обновить количество:
    • UpdateQuantityByValue принимает CartItem (по значению) и новое количество, затем устанавливает количество товара в новое значение
    • UpdateQuantityByPointer принимает *CartItem (указатель) и новое количество, затем устанавливает количество товара в новое значение
    Также создайте метод Total для CartItem, который возвращает цену, умноженную на количество.
  • main.go: Считайте данные о товаре из входных данных, создайте CartItem, а затем продемонстрируйте оба подхода к обновлению. Сначала попробуйте обновить с помощью функции, принимающей значение, и покажите результат, затем обновите с помощью функции, принимающей указатель, и снова покажите результат.

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

  • Строка 1: Название товара
  • Строка 2: Цена (float)
  • Строка 3: Начальное количество (integer)
  • Строка 4: Новое количество для установки (integer)

Формат вывода:

Initial: [Name] x[Quantity] = $[Total]
After value update: [Name] x[Quantity] = $[Total]
After pointer update: [Name] x[Quantity] = $[Total]

Например, для Laptop, 999.99, 1 и 3 ваш вывод должен быть таким:

Initial: Laptop x1 = $999.99
After value update: Laptop x1 = $999.99
After pointer update: Laptop x3 = $2999.97

Обратите внимание, как количество остается равным 1 после обновления по значению (функция изменила копию), но меняется на 3 после обновления по указателю (функция изменила оригинал). Форматируйте итоговые суммы с двумя знаками после запятой.

Шпаргалка

Go — это язык с передачей по значению. Когда вы передаете переменную в функцию, Go создает копию этого значения, поэтому изменения внутри функции не влияют на исходную переменную.

func double(n int) {
    n = n * 2
}

func main() {
    x := 10
    double(x)
    fmt.Println(x)  // 10 (unchanged)
}

То же самое относится и к структурам. Передача структуры в функцию копирует все её поля:

type Person struct {
    Name string
    Age  int
}

func birthday(p Person) {
    p.Age++
}

func main() {
    alice := Person{Name: "Alice", Age: 30}
    birthday(alice)
    fmt.Println(alice.Age)  // 30 (unchanged)
}

Чтобы изменить исходное значение, передайте указатель. Сам указатель копируется, но он по-прежнему указывает на тот же адрес в памяти:

func birthday(p *Person) {
    p.Age++
}

func main() {
    alice := Person{Name: "Alice", Age: 30}
    birthday(&alice)
    fmt.Println(alice.Age)  // 31 (modified!)
}

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

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

package main

import (
	"fmt"
)

func main() {
	// Чтение входных данных
	var name string
	var price float64
	var initialQty, newQty int

	fmt.Scanln(&name)
	fmt.Scanln(&price)
	fmt.Scanln(&initialQty)
	fmt.Scanln(&newQty)

	// TODO: Создайте CartItem с входными значениями

	// TODO: Выведите начальное состояние, используя формат:
	// fmt.Printf("Initial: %s x%d = $%.2f\n", ...)

	// TODO: Попробуйте обновить количество с помощью UpdateQuantityByValue
	// Затем выведите результат (обратите внимание, что количество не изменится)

	// TODO: Обновите количество с помощью UpdateQuantityByPointer
	// Затем выведите результат (обратите внимание, что количество изменится)
}
quiz iconПроверьте себя

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

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