Перейти к основному содержимому

Ошибки, которые стоит избегать на собеседовании Go

· 3 мин. чтения

Сегодня мы разберём классическую задачу на собеседовании для GoLang-разработчика, связанную с указателями и передачей аргументов в функции по значению. В ходе разбора инспировщик наглядно объясняет, почему при передаче указателя в функцию изменяется лишь локальная копия указателя, а не оригинальный объект, и демонстрирует, как использование указателя на указатель решает эту проблему. Это простое, но крайне важное упражнение помогает глубже понять работу памяти, стековых фреймов и механизмов передачи данных в Go.

Вопрос 1. Что выведет код с функцией changeName, принимающей указатель на структуру Person?

Таймкод: 00:00:00

Ответ собеседника: Правильный. Код выведет два раза "Иван", потому что в Go аргументы передаются по значению. Указатель копируется в функцию, и изменение локальной копии указателя не влияет на оригинальный указатель в main.

Правильный ответ:

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

Передача указателя по значению в Go

В Go всё передаётся по значению, включая указатели. Когда функция принимает *Person, она получает копию указателя. Это означает:

  • Копия указателя указывает на тот же самый объект в памяти
  • Если функция изменяет поля объекта через указатель (p.Name = "Владимир"), оригинал изменится
  • Если функция переприсваивает сам указатель (p = &Person{...}), изменится только локальная копия

Пример кода, который НЕ изменит оригинал:

package main

import "fmt"

type Person struct {
Name string
Age int
}

// Переприсваивание указателя — НЕ изменит оригинал
func changeName(p *Person) {
p = &Person{Name: "Владимир", Age: 25} // меняем локальную копию указателя
}

func main() {
person := Person{Name: "Иван", Age: 30}
fmt.Println(person.Name) // Иван
changeName(&person)
fmt.Println(person.Name) // Иван — не изменилось!
}

Пример кода, который ИЗМЕНИТ оригинал:

// Изменение полей через указатель — ИЗМЕНЯЕТ оригинал
func changeName(p *Person) {
p.Name = "Владимир" // меняем поле объекта, на который указывает копия
p.Age = 25
}

func main() {
person := Person{Name: "Иван", Age: 30}
fmt.Println(person.Name) // Иван
changeName(&person)
fmt.Println(person.Name) // Владимир — изменилось!
}

Как всё-таки переприсвоить указатель из функции

Если нужно именно заменить весь объект, есть два подхода:

// Способ 1: Указатель на указатель
func changeName(pp **Person) {
*pp = &Person{Name: "Владимир", Age: 25}
}

func main() {
person := &Person{Name: "Иван", Age: 30}
changeName(&person)
fmt.Println(person.Name) // Владимир
}

// Способ 2: Возврат нового указателя (идиоматичнее в Go)
func changeName(p *Person) *Person {
return &Person{Name: "Владимир", Age: 25}
}

func main() {
person := &Person{Name: "Иван", Age: 30}
person = changeName(person)
fmt.Println(person.Name) // Владимир
}

Вывод: Ответ собеседника верен — при переприсваивании указателя внутри функции оригинал не изменится, выведется "Иван" дважды. Для изменения оригинала нужно либо модифицировать поля объекта через указатель, либо использовать указатель на указатель, либо возвращать новый указатель.