Golang: Указатели
Go — язык с передачей аргументов по значению. Это значит, что при передаче переменной в функцию создаётся её копия, и любые изменения внутри функции не затрагивают оригинал. Но если нужно изменить переменную изнутри функции, используется указатель.
Передача по значению
func main() {
x := 5
change(x)
fmt.Println(x) // 5 — не изменилось
}
func change(n int) {
n = 10
}
В этом примере переменная x
передаётся по значению. Функция получает копию значения, и изменение n
не влияет на x
.
Передача указателя
func main() {
x := 5
change(&x)
// изменилось
fmt.Println(x) // => 10
}
func change(number *int) {
*number = 10
}
Здесь мы передаём в функцию не значение, а указатель на переменную x
. Оператор *
используется для получения значения по адресу (разыменования). Изменение *number
внутри функции приводит к изменению исходной переменной.
Если присвоить без разыменования внутри функции, то оригинальная переменная x
не изменится.
func change(number *int) {
number = 10 // не изменит оригинальную переменную
}
Как это работает
В Go всё передаётся по значению. Но если вы передаёте указатель, то копируется сам указатель (то есть адрес), и обе переменные указывают на одну и ту же область памяти.
x := 42
ptr := &x // адрес переменной
fmt.Println(ptr) // например, 0xc00001a0a8
fmt.Println(*ptr) // 42 — значение по адресу
Пример со структурой
Указатели особенно полезны при работе со структурами. Структуры могут содержать много полей, и копирование каждой из них при передаче в функцию может быть неэффективным. Кроме того, иногда нужно изменить структуру внутри функции.
Передача структуры по значению:
type User struct {
Name string
}
func rename(user User) {
user.Name = "Алиса"
}
func main() {
u := User{Name: "Боб"}
rename(u)
// Имя не изменилось
fmt.Println(u.Name) // => Боб
}
В этом примере структура User
передаётся по значению. Функция rename()
работает с копией, а не с оригиналом.
Передача указателя на структуру:
func rename(user *User) {
user.Name = "Алиса"
}
func main() {
u := User{Name: "Боб"}
rename(&u)
// Имя изменилось
fmt.Println(u.Name) // => Алиса
}
Теперь в функцию передаётся указатель на User
. Мы получаем доступ к оригинальной структуре и можем её изменить. Таким образом мы получаем привычное поведение для других языков, где составные данные передаются по ссылкам.
Go также позволяет обращаться к полям структуры через указатель без явного разыменования:
user.Name = "..." // работает
(*user).Name = "..." // тоже работает
Это сделано для удобства — компилятор сам вставляет *
, если это безопасно.
Когда использовать указатели
- Когда нужно изменить значение переменной или структуры внутри функции
- Когда объект большой, и копировать его неэффективно
Задание
Создайте структуру Account
с полями:
Owner
— имя владельца (строка),Balance
— баланс (целое число).
Реализуйте функцию Deposit()
, которая принимает указатель на Account
и сумму пополнения. Функция должна увеличить баланс на указанную сумму.
Пример
acc := Account{Owner: "Alice", Balance: 100}
Deposit(&acc, 50)
fmt.Println(acc.Balance) // 150
Полезное
Команда проекта находится в телеграм-сообществе. Там можно задать любой вопрос и повлиять на проект
Если вы зашли в тупик, то самое время поговорить с нашим асситентом Тота во вкладке "ИИ-помощник":
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи. В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в обратной связи нашего сообщества
Ваше упражнение проверяется по этим тестам
package solution
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestDeposit(t *testing.T) {
tests := []struct {
name string
initial int
amount int
expected int
}{
{"Positive deposit", 100, 50, 150},
{"Zero deposit", 100, 0, 100},
{"Negative deposit", 100, -20, 100},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
acc := Account{Owner: "Test", Balance: tt.initial}
Deposit(&acc, tt.amount)
assert.Equal(t, tt.expected, acc.Balance, "Balance mismatch in test case: %s", tt.name)
})
}
}
Решение учителя откроется через:
20:00
