Копирование и обмен значений
Что происходит, когда одну переменную присваивают другой, — место, где новички ошибаются чаще всего, и заблуждения тут самые живучие: состояние памяти в коде не видно, и его додумывают.
Присваивание копирует значение
Ситуация. Цену товара поднимают, а в карточке нужно показать и старую, и новую. Старое значение надо сохранить до того, как оно пропадёт.
package main
import "fmt"
func main() {
price := 250
oldPrice := price
price = 300
fmt.Println("Старая цена:", oldPrice)
fmt.Println("Новая цена:", price)
}Что увидите.
stagiaire@lab:~/gocourse/day02/scratch$ go run .
Старая цена: 250
Новая цена: 300Как это читать. oldPrice := price — «прочитай значение из price и положи копию в новую ячейку oldPrice». После строки 7 в памяти две независимые ячейки, в каждой 250. Строка 8 меняет только price:
после строки 7 после строки 8
┌─────┐ ┌─────┐
price │ 250 │ price │ 300 │
└─────┘ └─────┘
┌─────┐ ┌─────┐
oldPrice │ 250 │ oldPrice │ 250 │
└─────┘ └─────┘Три заблуждения
| Как кажется | Как на самом деле |
|---|---|
oldPrice := price связывает переменные: поменяется price — поменяется и oldPrice |
копируется значение в момент выполнения строки, дальше ячейки живут отдельно |
значение переезжает: после oldPrice := price в price пусто |
чтение ничего из ячейки не забирает — 250 лежит в обеих |
присваивание — как формула в электронной таблице: =A1 пересчитывается при изменении A1 |
ничего не пересчитывается: вычислилось один раз, записалось — и всё |
И про направление: a = b и b = a — разные команды. Слева — куда кладут, справа — откуда берут.
Попробуйте сейчас: что выведет копия цены?
Цель: предсказать вывод программы с копией цены и отправить ответ.
Не запуская:
Код для чтения · разбираем, набирать не нужноpackage main import "fmt" func main() { price := 250 oldPrice := price price = price + 50 oldPrice = oldPrice - 10 fmt.Println(price, oldPrice) }Нарисуйте таблицу с колонками
priceиoldPrice. Отправьте вывод целиком — два числа через пробел:▶ Выполните · выделенное замените своимcourse answer day02.q2 ВАШ_ОТВЕТВместо
ВАШ_ОТВЕТ— два числа через пробел, как их напечатает программа.Готово, когда: ответ отправлен — пункт проверки
q2.
Обмен значений
Ситуация. При приёмке перепутали ячейки: число коробок из A записали в B, и наоборот. Значения cellA и cellB надо поменять местами. Первое, что приходит в голову: положить B в A, потом A в B.
Попробуйте сейчас: что выведет обмен в две строки?
Цель: предсказать, что напечатает обмен «B в A, потом A в B», и отправить ответ.
Не запуская:
Код для чтения · разбираем, набирать не нужноpackage main import "fmt" func main() { cellA := 15 cellB := 40 cellA = cellB cellB = cellA fmt.Println(cellA, cellB) }Трассируйте по правилам прошлой страницы. Отправьте вывод — два числа через пробел:
▶ Выполните · выделенное замените своимcourse answer day02.q3 ВАШ_ОТВЕТВместо
ВАШ_ОТВЕТ— два числа через пробел, как их напечатает программа.Разбор — в следующем разделе. Читайте его после того, как отправите ответ.
Готово, когда: ответ отправлен — пункт проверки
q3.
Разбор: что получилось на самом деле
Разберём на других числах: та же программа, но в cellA 7, в cellB 12.
| шаг | строка | код | cellA | cellB |
|---|---|---|---|---|
| 1 | 6 | cellA := 7 |
7 | - |
| 2 | 7 | cellB := 12 |
7 | 12 |
| 3 | 8 | cellA = cellB |
12 | 12 |
| 4 | 9 | cellB = cellA |
12 | 12 |
| 5 | 10 | fmt.Println(cellA, cellB) |
12 | 12 |
На шаге 3 семёрка затёрта, и на шаге 4 взять её уже неоткуда. Обмен сломался на шаге 3, а не на 4.
Обмен через временную переменную
Как поменять местами содержимое двух полных коробок: нужна третья, пустая. Сначала в неё перекладывают то, что затрётся первым.
package main
import "fmt"
func main() {
cellA := 15
cellB := 40
saved := cellA
cellA = cellB
cellB = saved
fmt.Println("A:", cellA, "B:", cellB)
}stagiaire@lab:~/gocourse/day02/scratch$ go run .
A: 40 B: 15| шаг | строка | cellA | cellB | saved |
|---|---|---|---|---|
| 1 | 6 | 15 | - | - |
| 2 | 7 | 15 | 40 | - |
| 3 | 8 | 15 | 40 | 15 |
| 4 | 9 | 40 | 40 | 15 |
| 5 | 10 | 40 | 15 | 15 |
| 6 | 11 | 40 | 15 | 15 |
На шаге 4 cellA затирается, но копия 15 уже в saved, и на шаге 5 cellB берёт её оттуда. Порядок строк важен: переставьте 8 и 9 — и копия будет снята с уже затёртой ячейки.
Обмен в одну строку: a, b = b, a
package main
import "fmt"
func main() {
cellA := 15
cellB := 40
cellA, cellB = cellB, cellA
fmt.Println("A:", cellA, "B:", cellB)
}stagiaire@lab:~/gocourse/day02/scratch$ go run .
A: 40 B: 15Как это читать. Правило «сначала правая часть, потом запись» работает для всей строки сразу:
- Вычислить все значения справа по текущим ячейкам:
cellB— 40,cellA— 15. - Записать по порядку: первое значение в первую переменную, второе во вторую.
Пока идёт запись, правая часть уже посчитана, и ничего не затирается. В таблице трассировки такая строка — один шаг, на котором меняются обе колонки. В Go обычно пишут именно так; временная переменная помогает понять, что происходит внутри.
Попробуйте сейчас: что выведет обмен в одну строку?
Цель: предсказать вывод программы с обменом в одну строку и отправить ответ.
Не запуская:
Код для чтения · разбираем, набирать не нужноpackage main import "fmt" func main() { left := 4 right := 9 left, right = right, left right = right + left fmt.Println(left, right) }Отправьте вывод — два числа через пробел:
▶ Выполните · выделенное замените своимcourse answer day02.q4 ВАШ_ОТВЕТВместо
ВАШ_ОТВЕТ— два числа через пробел, как их напечатает программа.Готово, когда: ответ отправлен — пункт проверки
q4.
Что может пойти не так
| Что видите | Что это значит | Что делать |
|---|---|---|
| обе переменные стали одинаковыми, компилятор молчит | обмен без временной переменной | saved := cellA первой строкой или cellA, cellB = cellB, cellA |
declared and not used: saved |
временную переменную завели, но не взяли из неё значение: cellB = cellA вместо cellB = saved |
последний шаг обмена берёт значение из saved |
assignment mismatch: 2 variables but 1 value |
cellA, cellB = cellB |
справа столько значений, сколько переменных слева |
no new variables on left side of := |
cellA, cellB := cellB, cellA — обе переменные уже есть |
для обмена =, а не := |
| старая цена поменялась вместе с новой | копию сняли после изменения | копировать до изменения |
Попробуйте сейчас: задание
trace2.Цель: в
trace2/table.txtвсе?заменены значениями двух обменов подряд, найденными без запуска.Задание
trace2— два обмена подряд.
- Перейдите в папку задания:
▶ Выполнитеcd ~/gocourse/day02/trace2
- Заполните
table.txtбез запуска по правилам четвёртой страницы. Что меняет строкаsaved = 0, а что нет? СтрокаcellA, cellB = cellB, cellA— один шаг.- Потом запустите и сравните с последней строкой таблицы:
▶ Выполнитеgo run .Отладочная печать — как в
trace1, в копии вscratch;program.goне меняйте.Готово, когда: в
table.txtне осталось ни одного?, последняя строка совпала с выводомgo run .— пункт проверкиtrace2.
Попробуйте сейчас: задание
rotate.Цель:
rotate/main.goперекладывает три ячейки по кругу присваиваниями и печатает их до и после.Задание
rotate— три ячейки по кругу.
- Прочитайте условие:
▶ Выполнитеcd ~/gocourse/day02/rotate cat TASK.txt
- До кода выпишите на бумаге, что должно оказаться в каждой ячейке и что затрётся первым. Решить можно обоими способами этой страницы: через временную переменную или обменом в одну строку. Обмен в одну строку работает и для трёх имён — например,
x, y, z = z, x, y: сначала вычисляются все три значения справа, потом они записываются в переменные слева по порядку, и в таблице это тоже один шаг. Читать такую строку как «x в y, y в z» нельзя: слева — куда кладут, справа — откуда берут, так что здесьxполучает старое значениеz. Какое значение в какую ячейку должно приехать по вашему условию, решаете вы: сначала протрассируйте план на бумаге.- Напишите программу в
rotate/main.go. Числа изTASK.txtпишутся в коде один раз, при объявлении. Три строки печати после «После:» — те же самые, что после «До:»: те же подписи и те же переменные в том же порядке. НапечататьcellCна местеcellA— не перекладка, и проверка это заметит.- Запустите и сравните вывод с
TASK.txt:▶ Выполнитеgo run .Готово, когда: вывод совпал с
TASK.txtстрока в строку — пункты проверкиrotate.
Дальше: константы и правила имён — course next