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 — синтаксис объявления локальных переменных. Псевдоним типа — это грамматика, выражающая тот же тип с именем, соответствующим контексту кода.