Передача по значению и по ссылке
Часть раздела Объектно-ориентированное программирование путешествия по 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 (изменено!)
}Используйте указатели, когда вам нужно изменить исходное значение или при передаче больших структур, чтобы избежать дорогостоящего копирования. Используйте значения, когда вы хотите защитить исходные данные от непреднамеренных изменений.
Задание
ЛегкоДавайте создадим систему корзины покупок, которая демонстрирует разницу между передачей структур по значению и по указателю. Вы воочию увидите, как только параметры-указатели могут изменять исходные данные.
Вы организуете свой код в двух файлах:
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
// Затем выведите результат (обратите внимание, что количество изменится)
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker Pool