Переменные в скетчах Arduino: объявление, область видимости и типичные ошибки
Что такое переменные, как их объявлять, присваивать значения и управлять областью видимости в скетчах Arduino.
Переменная — это именованное место в памяти, где хранится какое-то значение. У каждой переменной есть три обязательных атрибута: имя, значение и тип. Например, вот такая строка называется объявлением переменной:
int pin = 13;
Здесь создаётся переменная с именем pin, значением 13 и типом int. В дальнейшем вы можете обращаться к этой переменной по имени — и компилятор сам подставит её текущее значение. Например, в строке:
pinMode(pin, OUTPUT);
именно значение pin (то есть 13) будет передано функции pinMode(). В данном случае переменная технически не обязательна — этот вариант сработает точно так же:
pinMode(13, OUTPUT);
Но у переменной есть важное преимущество: реальный номер пина указывается только один раз, а используется столько раз, сколько нужно. Если позже вы решите сменить пин 13 на пин 12, достаточно исправить одно место в коде, а не искать все вхождения числа 13 по всему скетчу. Кроме того, переменные можно называть осмысленно. Например, скетч управления RGB-светодиодом мог бы содержать переменные redPin, greenPin и bluePin — сразу понятно, что к чему.
Присваивание: изменяем значение переменной
Главное отличие переменной от константы — её значение можно менять в любой момент с помощью присваивания (знак =). Например:
pin = 12;
После этой строки переменная будет хранить значение 12. Обратите внимание: тип переменной при присваивании не указывается — он задаётся один раз при объявлении и не меняется. Имя переменной навсегда связано со своим типом, меняется только значение. [1]
Важно: переменную нужно объявить до того, как вы попытаетесь присвоить ей значение или использовать её. Если написать присваивание без предварительного объявления, компилятор выдаст ошибку:
error: pin was not declared in this scope
Когда вы присваиваете значение одной переменной другой, создаётся копия значения. Изменение одной переменной никак не влияет на другую. Например, после выполнения:
int pin = 13;
int pin2 = pin;
pin = 12;
только pin получит значение 12, а pin2 по-прежнему останется равным 13.
Область видимости (scope)
В сообщении об ошибке выше встречается слово «scope» — область видимости. Это та часть программы, в которой переменная доступна. Область видимости определяется местом объявления переменной.
Глобальные переменные
Если переменная нужна в нескольких функциях, объявите её в начале скетча — вне любых функций. Такая переменная называется глобальной:
int pin = 13;
void setup()
{
pinMode(pin, OUTPUT);
}
void loop()
{
digitalWrite(pin, HIGH);
}
Как видно, pin используется и в setup(), и в loop(). Обе функции работают с одной и той же переменной, поэтому изменение в одной функции сразу отражается в другой:
int pin = 13;
void setup()
{
pin = 12;
pinMode(pin, OUTPUT);
}
void loop()
{
digitalWrite(pin, HIGH);
}
Здесь digitalWrite(), вызванная из loop(), получит значение 12, потому что именно оно было присвоено переменной в setup().
Локальные переменные
Если переменная нужна только внутри одной функции, объявите её прямо там. Тогда её область видимости ограничится этой функцией:
void setup()
{
int pin = 13;
pinMode(pin, OUTPUT);
digitalWrite(pin, HIGH);
}
В этом случае переменная pin существует только внутри setup(). Если попытаться использовать её снаружи:
void loop()
{
digitalWrite(pin, LOW); // ошибка: переменная pin недоступна в этой области видимости.
}
компилятор снова скажет: error: 'pin' was not declared in this scope. Переменная объявлена в программе, но за пределами своей области видимости она просто не существует.
Почему не делать все переменные глобальными?
Может возникнуть соблазн объявлять всё глобально — зачем ограничивать доступ? Но у глобальных переменных есть серьёзный недостаток: их значение может быть изменено из любой точки программы. Чем больше скетч, тем сложнее отследить, где и когда переменная получила неожиданное значение.
Локальная переменная проще в отладке: вы точно знаете, что её значение может измениться только внутри одной конкретной функции. Это значительно сужает круг поиска, когда что-то идёт не так.
Практическое правило: используйте минимально возможную область видимости. Если переменная нужна только в одной функции — объявляйте её там. Глобальные переменные оправданы, когда данные действительно нужны нескольким функциям.
Типичные ошибки
- Использование переменной до объявления →
error: was not declared in this scope. Решение: объявите переменную выше по коду. - Указание типа при повторном присваивании — например, написать
int pin = 12;вместоpin = 12;внутри функции. Это создаст новую локальную переменную, а не изменит существующую. - Путаница с областью видимости — объявили переменную внутри
ifилиfor, а пытаетесь использовать её снаружи блока. Фигурные скобки{}всегда ограничивают область видимости.
[block scope] [size of variables]
[1] В некоторых языках, например в Python®, тип связан не с именем переменной, а с её значением — переменной можно присвоить значение любого типа. Это называется динамической типизацией. В Arduino (C++) тип фиксируется при объявлении и не меняется.