Указатели: почему & в Scanln?
Краткое описание
Объясняем, зачем нужен амперсанд & при работе с fmt.Scanln(). Знакомимся с концепцией указателей, адресами переменных и передачей данных по ссылке. Понимаем разницу между передачей по значению и по указателю.
Зачем нужен амперсанд (&) в Scanln?
var value int
fmt.Scanln(&value) // Почему & ?
Ответ: Scanln должна изменить переменную value, но функция получает копию. Чтобы изменить оригинал, нужно передать адрес переменной (&value).
Адреса переменных
Каждая переменная имеет адрес в памяти — место на стеке, где она хранится.
var volume int = 123
fmt.Printf("Значение: %d, Адрес: %p\n", volume, &volume)
// Значение: 123, Адрес: 0xc0000b4008
Спецификатор %p — показывает адрес переменной.
Оператор & — возвращает адрес переменной.
&volume // Адрес переменной volume
Проблема: передача по значению
Go копирует данные при передаче в функцию.
func inc(arg int) {
arg = arg + 1
fmt.Printf("Внутри inc: %d, адрес: %p\n", arg, &arg)
}
func main() {
var volume int = 123
fmt.Printf("До: %d, адрес: %p\n", volume, &volume)
inc(volume) // Передаётся КОПИЯ
fmt.Printf("После: %d, адрес: %p\n", volume, &volume)
}
Вывод:
До: 123, адрес: 0xc0000b4008
Внутри inc: 124, адрес: 0xc0000b4010 ← ДРУГОЙ адрес!
После: 123, адрес: 0xc0000b4008 ← НЕ изменилось!
Проблема: arg и volume — разные переменные в разных местах памяти. Изменение arg не влияет на volume.
Решение: указатели
Указатель — переменная, хранящая адрес другой переменной.
var volume int = 123
var pointer *int = &volume // Указатель на volume
Синтаксис:
*int— тип “указатель на int”&volume— адрес переменнойvolume
Работа с указателем
var volume int = 123
var pointer *int = &volume
fmt.Printf("volume: %d, адрес: %p\n", volume, &volume)
fmt.Printf("pointer хранит: %p, pointer расположен: %p\n", pointer, &pointer)
Вывод:
volume: 123, адрес: 0xc0000b4008
pointer хранит: 0xc0000b4008, pointer расположен: 0xc0000b4010
Визуализация:
Stack (функция main)
┌──────────────────────┐
│ volume = 123 │ адрес: 0x...008
├──────────────────────┤
│ pointer = 0x...008 │ адрес: 0x...010 (хранит адрес volume)
└──────────────────────┘
│
└─────┐ (pointer указывает на volume)
▼
Разыменование указателя
Оператор * — получить значение, на которое указывает указатель.
var volume int = 123
var pointer *int = &volume
fmt.Println(*pointer) // 123 (значение, на которое указывает pointer)
Изменение через указатель
var volume int = 123
var pointer *int = &volume
*pointer = 200 // Изменяем volume через pointer
fmt.Println(volume) // 200 (изменилось!)
fmt.Println(*pointer) // 200
Работает: *pointer и volume — одна и та же переменная в памяти!
Передача указателя в функцию
Чтобы функция могла изменить переменную, передаём указатель.
func inc(arg *int) { // Принимает указатель
*arg = *arg + 1 // Разыменование для чтения и записи
fmt.Printf("Внутри inc: %d, адрес: %p\n", *arg, arg)
}
func main() {
var volume int = 123
fmt.Printf("До: %d, адрес: %p\n", volume, &volume)
inc(&volume) // Передаём АДРЕС
fmt.Printf("После: %d, адрес: %p\n", volume, &volume)
}
Вывод:
До: 123, адрес: 0xc0000b4008
Внутри inc: 124, адрес: 0xc0000b4008 ← ТОТ ЖЕ адрес!
После: 124, адрес: 0xc0000b4008 ← ИЗМЕНИЛОСЬ!
Работает: функция получает адрес, работает с оригиналом.
Полный пример
package main
import "fmt"
func inc(arg *int) {
fmt.Printf("2. Внутри inc: значение=%d, адрес=%p\n", *arg, arg)
*arg = *arg + 1
fmt.Printf("3. После изменения: значение=%d, адрес=%p\n", *arg, arg)
}
func main() {
var volume int = 123
fmt.Printf("1. До: значение=%d, адрес=%p\n", volume, &volume)
inc(&volume) // Передаём адрес
fmt.Printf("4. После: значение=%d, адрес=%p\n", volume, &volume)
}
Вывод:
1. До: значение=123, адрес=0xc0000b4008
2. Внутри inc: значение=123, адрес=0xc0000b4008
3. После изменения: значение=124, адрес=0xc0000b4008
4. После: значение=124, адрес=0xc0000b4008
Все адреса одинаковые → работаем с одной переменной.
Теперь про Scanln
var value int
fmt.Scanln(&value) // Передаём адрес
Что происходит:
Scanlnпринимает*int(указатель)- Получает адрес переменной
value - Читает ввод пользователя
- Записывает в адрес через разыменование:
*arg = введённое_значение - Переменная
valueизменяется
Без & не сработает:
var value int
fmt.Scanln(value) // ❌ ОШИБКА: передана копия, а нужен адрес
Синтаксис указателей
| Символ | Значение | Пример |
|---|---|---|
*int |
Тип “указатель на int” | var ptr *int |
&x |
Адрес переменной x |
ptr = &x |
*ptr |
Разыменование (получить значение) | val := *ptr |
*ptr = 10 |
Запись по адресу | *ptr = 10 |
Ключевые моменты
1. Go передаёт данные по значению (копирует)
func foo(x int) {
x = 10 // Изменяет копию
}
2. Чтобы изменить оригинал — нужен указатель
func foo(x *int) {
*x = 10 // Изменяет оригинал
}
// Вызов: foo(&variable)
3. Адрес — &переменная
&volume // Адрес переменной volume
4. Разыменование — *указатель
*pointer // Значение, на которое указывает pointer
5. Scanln требует адрес
fmt.Scanln(&value) // & обязателен!
6. Тип указателя — *тип
var ptr *int // Указатель на int
var ptr2 *string // Указатель на string
7. Один адрес = одна переменная
Если адреса одинаковые → работаем с одной переменной в памяти.
Визуальная схема
Передача по значению (копирование)
main() inc(arg int)
┌──────────────┐ ┌──────────────┐
│ volume = 123 │ ──────→ │ arg = 123 │ (копия)
│ адрес: 0x008 │ │ адрес: 0x010 │
└──────────────┘ └──────────────┘
arg = 124
volume остаётся 123 ❌
Передача по указателю (адрес)
main() inc(arg *int)
┌──────────────┐ ┌──────────────┐
│ volume = 123 │ ────┐ │ arg = 0x008 │ (адрес)
│ адрес: 0x008 │ └──→│ │
└──────────────┘ └──────────────┘
▲ │
└────────────────────────┘
*arg = 124
volume становится 124 ✅
Что запомнить
&— получить адрес переменной*тип— объявить указатель*ptr— разыменовать указатель (получить/изменить значение)- Go копирует данные при передаче в функцию
- Указатели позволяют изменять оригинал
fmt.Scanln(&var)— нужен&, чтобы функция могла изменить переменную- Адрес указывает на место в памяти
- Одинаковые адреса = одна переменная
Для новичков
Не обязательно глубоко понимать указатели на старте. Достаточно знать:
✅ fmt.Scanln(&переменная) — всегда пишем &
✅ & нужен, чтобы функция могла изменить переменную
✅ Позже указатели станут понятнее с практикой
Главное: можете использовать Scanln, не думая о деталях. Понимание придёт с опытом!