2. Переменные, типы и область действия
Изучите локальные переменные, целочисленную область действия, bool и области действия.
Wave Foundation
Обработка значений по имени
Если вы напишете цены сразу в нескольких местах, вам придется найти их все, когда вы измените цену. Переменная — это пространство хранения, которое дает имена значениям и позволяет вам читать и изменять их, используя эти имена. В этой главе вы узнаете об области видимости объявлений, присвоений, типов и области действия видимых имен внутри блоков.
Каждая из приведенных ниже программ является частью отдельного main.wave. Сохраните один пример, запустите его как wavec run main.wave и замените его следующим примером.
Объявление и инициализация
fun main() {
var price: i32 = 1200;
var quantity: i32 = 3;
println("price={}", price);
println("quantity={}", quantity);
println("total={}", price * quantity);
}
Результат выполнения:
price=1200
quantity=3
total=3600
Прочтите декларацию в четырех частях.
| часть | этот пример | роль |
|---|---|---|
| Ключевое слово объявления | var | Создать локальную переменную |
| имя | price | Идентификатор, который будет использоваться позже |
| тип | i32 | Тип и диапазон значений для хранения |
| начальное значение | 1200 | Первое значение для сохранения |
Двоеточие перед типом и знак равенства перед начальным значением имеют разные роли. Сделайте свое имя значимым. В этом примере price — цена за единицу, а quantity — количество. Даже если один и тот же i32 используется взаимозаменяемо, неправильные вычисления могут быть сделаны без грамматических ошибок.
Назначение — это не формула, поддерживающая отношения.
Если вы сохраните результат расчета в переменной, будет введено значение в этой точке. Он не запоминает расчеты и автоматически пересчитывает их позже.
fun main() {
var price: i32 = 1200;
var quantity: i32 = 3;
var total: i32 = price * quantity;
quantity = 5;
println("before recalculation={}", total);
total = price * quantity;
println("after recalculation={}", total);
}
Результат выполнения:
before recalculation=3600
after recalculation=6000
quantity = 5 записывает новое значение в уже существующую переменную. Его следует отличать от повторного объявления типа var quantity. total также равно 3600 перед повторной заменой. Если программа должна поддерживать отношения между несколькими переменными, ее следует написать так, чтобы выполнять вычисления при изменении отношений.
Вычислить следующее значение из предыдущего значения
Сначала вычислите правую часть оператора присваивания и запишите результат в область памяти слева. В отличие от математических уравнений, count = count + 1 является допустимым обновлением.
fun main() {
var count: i32 = 0;
count = count + 1;
count += 2;
count *= 3;
println("{}", count);
}
Результат выполнения:
9
Прогрессия значений: 0 → 1 → 3 → 9. +=, *= экспресс-вычисление и совместное хранение. Если вы запишете порядок расчета, то сможете узнать, на каком этапе ваши мысли были иными, когда результаты отличаются от ожидаемых.
Ширина и знак целочисленных типов
Если он начинается с i, он становится signed, а если он начинается с u, он становится unsigned. Последнее число — количество бит. По мере увеличения количества битов увеличивается диапазон, который может быть выражен, и объем памяти также увеличивается.
| тип | минимальное значение | максимальное значение | Пример использования |
|---|---|---|---|
| i8 | -128 | 127 | маленькое знаковое значение |
| u8 | 0 | 255 | один байт |
| i16 | -32768 | 32767 | небольшие целочисленные данные |
| u16 | 0 | 65535 | Порт/16-битное поле |
| i32 | -2147483648 | 2147483647 | Общие вычисления малых целых чисел |
| u32 | 0 | 4294967295 | 32-битное поле |
Wave также предоставляет целые числа со знаком и без знака длиной 64, 128, 256, 512 и 1024 бита. Более широкий тип не делает каждое вычисление безопасным: результат все равно может выйти за пределы выбранного диапазона. Сначала выберите нужный вам диапазон. isz и usz соответствуют ширине целевого адреса.
Необходимо проводить различие между хранением большого литерала в маленьком типе и намеренным отбрасыванием битов с помощью cast. Преобразование к меньшему типу просто для устранения ошибок может изменить само значение. Преобразования рассматриваются в следующей главе.
Типы с плавающей запятой и bool
f32·f64 — числа с плавающей запятой. В отличие от целых чисел, они могут представлять десятичные части, но не могут точно хранить все десятичные числа. Это одна из причин, почему суммы управляются небольшими целыми единицами.
bool представляет истину и ложь. Вы можете дать своему условию имя, сохранив результаты сравнения следующим образом:
fun main() {
var balance: i32 = 5000;
var cost: i32 = 3600;
var can_buy: bool = balance >= cost;
if (can_buy) {
println("purchase allowed");
} else {
println("not enough money");
}
}
Результат выполнения:
purchase allowed
can_buy не следует автоматически за изменениями в balance и cost. После того, как два значения поменяны местами, сравнение выполняется снова, если необходимо текущее состояние.
неинициализированное пространство хранения
var value: i32; — это форма, в которой указывается только пространство для хранения. Перед чтением необходимо записать допустимое значение. Не думайте, что 0 вставляется автоматически только потому, что вы его объявили. В вводном курсе легче понять, если значение сразу известно и инициализируется одновременно с объявлением.
В вызовах библиотеки, которые получают значения в качестве выходных аргументов, бывают случаи, когда сначала объявляется пробел, а затем в случае успеха считывается. В это время вы должны проверить успешный результат функции. Избегайте ошибки чтения неинициализированного вывода после неудачного вызова.
Допустимый диапазон блоков и имен
Блок — это область кода, заключенная в фигурные скобки. Если вы объявите внутри новую переменную с тем же именем, новая переменная будет использоваться внутри этого блока. Это называется shadowing.
fun main() {
var value: i32 = 10;
if (true) {
var value: i32 = value + 5;
println("inner={}", value);
value += 1;
println("inner changed={}", value);
}
println("outer={}", value);
}
Результат выполнения:
inner=15
inner changed=16
outer=10
Начальное выражение value + 5 во внутреннем объявлении считывает внешнее value. После завершения инициализации новой переменной внутреннее значение value равно 15. Даже если вы измените внутреннее значение на 16, внешнее пространство памяти не изменится. После блока вы снова увидите value снаружи.
И наоборот, если вы выполняете только value += 1 без var во внутреннем блоке, существующие видимые переменные будут изменены. Посмотрите на ключевое слово, чтобы определить, является ли оно новым объявлением или изменением существующего значения.
Локальные переменные и хранилище верхнего уровня
Вне функции вы можете использовать const и static. const представляет собой постоянное значение, а static — это объем памяти, сохраняемый во время выполнения. Их нельзя объявлять везде так же, как локальные переменные.
const LIMIT: i32 = 3;
static visits: i32 = 0;
fun visit() {
visits += 1;
println("visit={}", visits);
}
fun main() {
visit();
visit();
println("limit={}", LIMIT);
}
Результат выполнения:
visit=1
visit=2
limit=3
Даже если visit вызывается дважды, static не будет сбрасываться в 0 при каждом вызове. С другой стороны, если вы объявите его как var visits: i32 = 0; внутри функции, он будет инициализировать локальное хранилище каждый раз, когда вы его вызываете. Общее изменяемое состояние может затруднить отслеживание поведения, поэтому сначала подумайте, можно ли его разрешить с помощью входных и выходных данных функции.
распространенные ошибки
- Когда объявление и присвоение путаются и одно и то же имя объявляется снова без необходимости.
- Если вы считаете, что переменная, хранящая результат расчета, автоматически следует за изменениями входной переменной.
- Если вы думаете, что, поскольку тип один и тот же, такие единицы, как число и количество байтов, также одинаковы.
- При чтении без инициализации или при чтении выходного аргумента при сбое функции.
- Если вы думаете, что имя локальной переменной видно за пределами блока.
При поиске ошибочного имени проверьте позицию объявления имени и диапазон скобок.
Упражнение: расчет изменений запасов
Первоначальный запас составляет 20 единиц, продается дважды, по 3 единицы каждый. Распечатайте оставшиеся запасы и общее количество проданных единиц. Всякий раз при изменении запасов обновляются одни и те же переменные, а объем продаж также накапливается отдельно.
Полное решение
fun main() {
var stock: i32 = 20;
var sold: i32 = 0;
var order: i32 = 3;
stock -= order;
sold += order;
stock -= order;
sold += order;
println("stock={} sold={}", stock, sold);
}
Результат выполнения:
stock=14 sold=6
Запасы и объем продаж должны меняться одновременно. Обновление любого из них нарушает связь между значениями. Обработку повторяющихся заказов мы сгруппируем с помощью обучающих функций и операторов цикла.
Целочисленные типы и типы с плавающей запятой
Целочисленные типы следующие:
- Подпись:
i8,i16,i32,i64,i128,i256,i512,i1024 - Без знака:
u8,u16,u32,u64,u128,u256,u512,u1024 - Целое число размера адреса:
isz,usz - С плавающей точкой:
f32,f64
isz — это целочисленный тип со знаком, соответствующий размеру адреса, а usz — это целочисленный тип без знака, соответствующий размеру адреса.
Другие встроенные типы
| тип | Использование |
|---|---|
bool |
true или false |
char |
8-битное символьное значение без знака. Не произвольный тип кодовой точки Unicode |
byte |
8-битное байтовое значение |
str |
Строковые байты, оканчивающиеся на NUL |
ptr<T> |
Указатель таргетинга T |
array<T, N> |
Массив фиксированной длины с типом элемента T и длиной N |
В расположениях типов также можно использовать определяемые пользователем структуры, перечисления и псевдонимы типов.
var — синтаксис объявления локальных переменных. Псевдоним типа — это грамматика, выражающая тот же тип с именем, соответствующим контексту кода.