День 02 · Переменные и присваивание · страница 5 из 6

Копирование и обмен значений

Что происходит, когда одну переменную присваивают другой, — место, где новички ошибаются чаще всего, и заблуждения тут самые живучие: состояние памяти в коде не видно, и его додумывают.


Присваивание копирует значение

Ситуация. Цену товара поднимают, а в карточке нужно показать и старую, и новую. Старое значение надо сохранить до того, как оно пропадёт.

Код для чтения · разбираем, набирать не нужно
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

Как это читать. Правило «сначала правая часть, потом запись» работает для всей строки сразу:

  1. Вычислить все значения справа по текущим ячейкам: cellB — 40, cellA — 15.
  2. Записать по порядку: первое значение в первую переменную, второе во вторую.

Пока идёт запись, правая часть уже посчитана, и ничего не затирается. В таблице трассировки такая строка — один шаг, на котором меняются обе колонки. В 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 — два обмена подряд.

  1. Перейдите в папку задания:
▶ Выполните
cd ~/gocourse/day02/trace2
  1. Заполните table.txt без запуска по правилам четвёртой страницы. Что меняет строка saved = 0, а что нет? Строка cellA, cellB = cellB, cellA — один шаг.
  2. Потом запустите и сравните с последней строкой таблицы:
▶ Выполните
go run .

Отладочная печать — как в trace1, в копии в scratch; program.go не меняйте.

Готово, когда: в table.txt не осталось ни одного ?, последняя строка совпала с выводом go run . — пункт проверки trace2.


Попробуйте сейчас: задание rotate.

Цель: rotate/main.go перекладывает три ячейки по кругу присваиваниями и печатает их до и после.

Задание rotate — три ячейки по кругу.

  1. Прочитайте условие:
▶ Выполните
cd ~/gocourse/day02/rotate
cat TASK.txt
  1. До кода выпишите на бумаге, что должно оказаться в каждой ячейке и что затрётся первым. Решить можно обоими способами этой страницы: через временную переменную или обменом в одну строку. Обмен в одну строку работает и для трёх имён — например, x, y, z = z, x, y: сначала вычисляются все три значения справа, потом они записываются в переменные слева по порядку, и в таблице это тоже один шаг. Читать такую строку как «x в y, y в z» нельзя: слева — куда кладут, справа — откуда берут, так что здесь x получает старое значение z. Какое значение в какую ячейку должно приехать по вашему условию, решаете вы: сначала протрассируйте план на бумаге.
  2. Напишите программу в rotate/main.go. Числа из TASK.txt пишутся в коде один раз, при объявлении. Три строки печати после «После:» — те же самые, что после «До:»: те же подписи и те же переменные в том же порядке. Напечатать cellC на месте cellA — не перекладка, и проверка это заметит.
  3. Запустите и сравните вывод с TASK.txt:
▶ Выполните
go run .

Готово, когда: вывод совпал с TASK.txt строка в строку — пункты проверки rotate.


Дальше: константы и правила имён — course next