
Итак, йоу, юзеры!
Я Hilrein, 18-летний разработчик мобильных и веб-приложений. В этой статье я расскажу вам про базовый Dart - язык программирования, лежащий в основе Flutter. Он отлично подходит как для новичков, так и для тех, кто хочет перейти в кроссплатформенную мобильную разработку.
Эта статья рассчитана на начинающих и охватывает всё, что нужно знать до перехода к объектно-ориентированному программированию (ООП):
Содержание
Переменные и константы
Компиляция и выполнение
Типы данных
Почему var или dynamic не всегда лучший выбор
Операции с переменными
Условные операторы
Циклы
Функции и основы работы с ними
Немного про null safety(Безопасность от
null
)
Если ты только начинаешь путь в программировании - отлично, добро пожаловать! А если хочешь сначала разобраться с Dart перед тем, как прыгнуть во Flutter - тоже прекрасно: эта статья станет отличной отправной точкой.
p.s. Если будут какие-то темы непонятны, дайте фидбек. И я обязательно выпущу подробную статью о той или иной теме.
Погнали!
🧺 Переменные: что это и зачем они нужны?
Переменная - это просто имя, под которым хранятся данные. Представь себе коробку с надписью: ты кладёшь в неё что-то (число, текст, список), а потом достаёшь, когда нужно.
Например:
Коробка с надписью
одежда
-> в ней лежит одежда.Коробка с надписью
игрушки
-> угадаешь, что там? Конечно же игрушки
В коде всё так же. Мы создаём переменную (коробку), даём ей имя и кладём туда нужное значение. Это помогает удобно хранить и использовать данные в программе.
Объявлять переменные можно с помощью ключевого слова var
:
var имяПеременной = значение;
Если я хочу создать переменную с именем water с числом 500, то в коде это выглядит так:
var water = 500;
🧊 Константы: что это такое?
Если переменная - как мы поняли что это коробка, куда можно класть всё, что захочешь, то константа - это коробка, которую ты один раз закрыл и больше не открываешь и не изменишь значение🔒
Положил туда что-то и больше изменить не можешь.
Это нужно, когда значение не должно меняться. Например:
Максимальное количество очков в игре 🎮
Название приложения 🏷️
Скорость света (ну вдруг ты пишешь симулятор Вселенной 💫)
В Dart есть два способа задать константу:
final
- значение устанавливается один раз, но может быть определено во время выполнения (в ходе чтения статьи я объясню подробнее):
final имя = значение;
//пример:
final houseHeight = 100 //houseHeight - высотаДома
/*
house - дом
height - высота
*/
const
- значение задаётся на этапе компиляции и никогда не меняется (в ходе чтения статьи я объясню подробнее):
const имя = значение;
//пример:
const pi = 3.12 // pi - число пи
Разница?
final - runtime constant
const - compile-time constant
Что такое runtime и compile-time
Для ясности:
runtime -> во время выполнения (когда программа уже работает)
compile-time -> во время компиляции (до того, как программа запустится)
Запуск любого кода - это не мгновенный процесс, а скорее путешествие, состоящее из двух больших остановок: сначала этап компиляции, а затем -> этап выполнения.
Компиляция программы(Сompile-time)
Компиляция - Это самый первый шаг, когда компьютер проверяет твой код ещё до того, как программа вообще запустилась. Представь, что у тебя есть специальный помощник, который называется компилятор. Он как бы читает весь твой написанный код, строку за строкой. Его главная задача - убедиться, что всё написано правильно, нет ли синтаксических ошибок (например, забытой точки с запятой), и можно ли вообще собрать из этого набор инструкций, который компьютер сможет понять и выполнить.
Если ты написал что-то неправильно, компилятор сразу же это заметит и скажет: "Так нельзя!" В этом случае программа просто не запустится, пока ты не исправишь ошибки.
Выполнение программы(Runtime)
Итак, после того как компилятор (наш помощник из предыдущей части) проверил весь код и убедился, что всё в порядке - никаких ошибок, и программа готова к запуску, - наступает очередь рантайма.
Рантайм - это тот самый момент, когда твоя программа начинает работать и выполнять твой код, который ты написал.
Простыми словами: если compile-time - это проверка кода перед выполнением то runtime - это само выполнение кода. Именно на этапе рантайма твоя программа "оживает" и начинает делать то, что ты написал.
Теперь я надеюсь что ты понял разницу. И давай перейдем опять к final и const
final это runtime constant, и ее значение присваиваеться во время выполнения программы
const это compile-time constanta, и ее значение писваиваеться во время компиляции
💡 Что такое типы данных?
Вот мы и подошли к теме типов данных! 😊
Когда ты создаешь переменную, компьютеру очень важно знать, что именно ты собираешься хранить в этой коробочке. Это может быть число, текст, или что-то еще. Именно для этого и нужны типы данных.
Давай разберу на примере
int apples = 5; // В этой переменной мы будем хранить целое число
const double pi = 3.14; // А здесь константа - с числом дробной частью
String author = 'Александр Сергеевич Пушкин'; // Это переменная для текста
bool isSunny = true; // А тут - только "правда" или "ложь"
dynamic word = 'Привет' // Здесь переменная с словом "Привет"
Теперь давай разберемся, что означает каждый из этих типов:
int
(от слова "integer") хранит только целые числа. Это все числа без дробной части, например:5
,100
,3
. Числа вроде3.0
или3.14
уже считаются числами с плавающей точкой и дляint
не подходят.double
(илиfloat
в некоторых языках) хранит числа с плавающей точкой (то есть числа с дробной частью), например:3.14
,0.5
,12.7
.String
(от слова "строка") хранит текст, предложения или слова. Текст всегда заключается в кавычки (обычно двойные""
, но иногда и одинарные''
в зависимости от языка и разработчика).bool
(от слова "boolean") хранит в себе только два возможных значения:true
(правда) илиfalse
(ложь). Этот тип часто используется для проверки условий.dynamic
это уникальный тип данных, который может в себе хранить все типы данных, которые я приводил выше. Но он сам устанавливает тот тип данных, который у него в значении. Это означает, что тип переменнойdynamic
определяется только во время выполнения программы (в рантайме), а не на этапе компиляции.
Почему важно указывать тип данных? (И почему var или dynamic не всегда лучший выбор)
Как ты уже заметил, мы создали переменные без var
, а вместо него явно указали int
, double
, String
и bool
. Не бойся, это не приведет к ошибке, а наоборот - улучшит оптимизацию твоей программы! И вот почему:
Ты же уже знаешь, что такое компиляция? Так вот, если ты укажешь тип данных явно (с помощью int
, double
, String
, bool
и т.д.), то компилятор точно знает, какой объем памяти нужно выделить для этой переменной. Он заранее понимает, что именно будет храниться в "коробочке".
А это как раз позволяет компилятору проводить более эффективные оптимизации на этапе компиляции. Он может "подготовить" программу лучше, потому что у него есть вся необходимая информация.
Если же использовать var
, то это уже неявное указание типа. Компилятору придется догадываться о типе переменной, основываясь на том значении, которое ты ей присвоил. Или же, в некоторых случаях, тип будет определяться только во время выполнения программы (в рантайме). Это может замедлить процесс компиляции и сделать оптимизации менее эффективными, так как компилятор не имеет полной информации заранее.
Теперь что не так с dynamic
? А дело в том, что, в отличие от var
(который обычно определяет тип на этапе компиляции, если это возможно), dynamic
всегда откладывает определение типа до рантайма. Это означает, что компилятор вообще не знает, какой тип данных будет храниться в переменной dynamic
до самого момента выполнения программы.
Это дает большую гибкость, но имеет свою цену:
Потеря оптимизации: Компилятор не может заранее оптимизировать код, так как не знает точный тип данных, что может привести к менее производительной программе.
Ошибки в рантайме: Все проверки на совместимость типов происходят только во время выполнения. Это значит, что если ты попытаешься сделать что-то неподходящее для текущего типа данных (например, умножить текст на число), ошибка проявится только тогда, когда программа уже работает, а не на этапе компиляции. Это усложняет поиск и исправление ошибок.
Поэтому, явно указывая тип, ты помогаешь компилятору работать быстрее и эффективнее, что в итоге делает твою программу более производительной и надежной!
Операции с переменными
Теперь когда мы умеет создавать переменные, константы а также явно указывать им тип данных, пришло время научиться что-то с ними делать. Переменные нужны не просто для хранения информации, но и для того, чтобы мы могли ею манипулировать - изменять, комбинировать, использовать в вычислениях.
Давай посмотрим на самые основные операции, которые ты будешь часто использовать:
1. Присваивание
Это самая базовая операция. Она позволяет присвоить(положить) новое значение в переменную.
int score = 0; // Создали переменную 'score' и присвоили ей 0
score = 100; // Теперь 'score' равно 100. Старое значение (0) стерлось.
Важно обратить внимание, что такое провернуть с константами(final, const) - НЕЛЬЗЯ!!!
2. Арифметические операции
С числами можно выполнять все привычные математические действия:
+
(сложение)-
(вычитание)*
(умножение)/
(деление)%
(остаток от деления)\
int num1 = 10;
int num2 = 5;
int sum = num1 + num2; // sum будет равно 15
int difference = num1 - num2; // difference будет равно 5
int product = num1 * num2; // product будет равно 50
double quotient = num1 / num2; // quotient будет равно 2.0
int remainder = num1 % 3; // remainder будет равно 1 (10 / 3 = 3 и 1 в остатке)
3. Конкатенация (для строк)
Конкатенация - оно нужно для объединения строк. С помощью оператора +
ты можешь "склеивать" текстовые переменные или текст с другими типами данных.
String firstName = 'Иван';
String lastName = 'Иванов';
String fullName = firstName + ' ' + lastName; // fullName будет равно "Иван Иванов"
String greeting = 'Привет, ' + firstName + '!'; // greeting будет равно "Привет, Иван!"
int age = 25;
String info = 'Мне ' + age + ' лет.'; // info будет равно "Мне 25 лет."
4. Логические операции (для bool)
С булевыми значениями (true/false) можно выполнять логические операции, чтобы проверять условия:
&&
(И - "and")||
(ИЛИ - "or")!
(НЕ - "not"): Он может меняетtrue
наfalse
илиfalse
наtrue
.
bool isRaining = true;
bool hasUmbrella = false;
// isRaining && hasUmbrella будет false (Идет дождь И есть зонт -> false, потому что зонта нет)
bool canGoOutside = isRaining && hasUmbrella;
print(canGoOutside);
// isRaining || hasUmbrella будет true (Идет дождь ИЛИ есть зонт -> true, потому что идет дождь)
bool needToDecide = isRaining || hasUmbrella;
print(needToDecide);
// !isRaining будет false (НЕ идет дождь -> false)
bool notRaining = !isRaining;
print(notRaining);
Это лишь основные операции, но они составляют фундамент для большинства действий, которые ты будешь выполнять со своими переменными в программах. Практикуйся, и ты быстро освоишься!
Условные операторы
В программировании очень часто нужно принимать решения: "Если что-то верно, то сделай одно, иначе - сделай что-то другое". Именно для этого существуют условные операторы. Они позволяют твоей программе быть "умной" и реагировать на разные ситуации.
Представь, что ты даешь компьютеру инструкции, как будто говоришь: "Если на улице идет дождь, возьми зонт. Иначе, если светит солнце, надень кепку. В любом другом случае, просто оставайся дома."
В коде это выглядит с помощью ключевых слов if
(если), else if
(иначе если) и else
(иначе).
1. if (Если)
Это самый простой условный оператор. Код внутри if
выполнится только в том случае, если условие в скобках ()
является true
(правдой).
int temperature = 25;
if (temperature > 20) { // Этот код выполнится, потому что 25 больше 20 // (temperature > 20) это true print('На улице тепло!');
}
int score = 80;
if (score >= 90) { // Этот код НЕ выполнится, потому что 80 НЕ больше или равно 90 // (score >= 90) это false print('Отлично!');
}
Обрати внимание: условие внутри if
всегда должно быть выражением, которое в итоге дает true
или false
(то есть, это булевое выражение).
2. else (Иначе)
Оператор else
используется вместе с if
. Он позволяет указать, что делать, если условие в if
оказалось false
(ложным).
int age = 17;
if (age >= 18) { // Этот код сработает, при условии если возраст(age) больше или равно 18 print('Доступ разрешен.');
} else { print('Доступ запрещен.');
}
// В данном случае, так как age = 17, выполнится блок 'else'.
3. else if (Иначе если)
Когда у тебя есть несколько возможных условий, которые нужно проверить по очереди, ты используешь else if
. Компьютер проверяет условия сверху вниз: если первое if
ложно, он переходит к else if
. Если else if
ложно, он может перейти к следующему else if
или к else
.
int time = 14;
if (time < 12) { // Проверяем: время меньше 12? (14 < 12) -> false print('Доброе утро!');
} else if (time < 18) { // Проверяем: время меньше 18? (14 < 18) -> true // Этот блок выполнится! // print('Добрый день!');
} else { // Этот блок не выполнится, так как предыдущий else if был true print('Добрый вечер!');
}
// В этом примере программа выведет "Добрый день!".
Ты можешь использовать сколько угодно else if
между if
и else
. Важно помнить, что как только одно из условий (if
или else if
) окажется true
, соответствующий блок кода выполнится, и остальные else if
и else
будут проигнорированы.
Условные операторы - это мощный инструмент, который делает твои программы гибкими и способными принимать решения.
Циклы
Представь, что тебе нужно выполнить одно и то же действие много-много раз. Например, вывести на экран числа от 1 до 100. Первое решение которое тебе может быть придет в голову это сделать так:
print('число 1');
print('число 2');
print('число 3');
print('число 4');
print('число 5');
Писать одно и то же сто раз было бы очень долго, неудобно и снизило бы поддержку кода, не так ли? Именно для таких случаев в программировании существуют циклы.
Циклы позволяют выполнять один и тот же блок кода повторно, пока не будет выполнено какое-то условие.
Существует несколько видов циклов, каждый из которых подходит для разных задач. Давай рассмотрим самые основные:
1. Цикл for
Цикл for
идеально подходит, когда ты заранее знаешь, сколько раз нужно выполнить действие. Он состоит из трех основных частей:
Инициализация: Что сделать в самом начале (например, создать счетчик).
Условие: Пока это условие верно(true), цикл будет продолжаться.
Итерация: Что делать после каждого выполнения блока кода (например, увеличить счетчик или что нибудь другое).
// Пример: вывести числа от 1 до 30
for (int i = 1; i <= 30; i++) { // i = 1 (начало) // i <= 5 (условие: пока i меньше или равно 5) // i++ (после каждого шага увеличиваем i на 1) print('Число: ' + i); // Выведет: Число: 1, Число: 2, Число: 3, Число: 4, Число: 5 и так до 30 раз
}
Здесь i++
- это сокращенная запись i = i + 1
.
2. Цикл while
Цикл while
используется, когда ты не знаешь заранее, сколько раз нужно будет повторять действие, но знаешь, какое условие должно быть истинным, чтобы цикл продолжался. Код внутри while
будет выполняться до тех пор, пока условие в скобках ()
является true
.
// Пример: отсчет до запуска ракеты
int countdown = 5;
while (countdown > 0) { // Условие: пока countdown > 0 -> true(выполнять цикл) если же countdown < 0 -> false(прервать работу цикла) // print('Обратный отсчет: ' + countdown); // Выведет: Обратный отсчет: 5, 4, 3, 2, 1 countdown--; // Уменьшаем countdown на 1 после каждого шага
}
// когда цикл полностью выполниться, то выведет в консоли 'Запуск!'
print("Запуск!");
Важно: если ты забудешь изменить переменную, которая участвует в условии while
(например, countdown--
), то цикл может стать бесконечным, и программа зависнет
Цикл
do-while
Цикл do-while
похож на while
, но с одним важным отличием: он всегда выполняет блок а потом уже проверяет условие. Если на примере while он перед деланием что то(do) он сначала проверял а потом делал, то do-while сначала делает а потом уже проверяет. Если что, у do-while условие проверяется в конце цикла.
Представь, что ты пытаешься открыть дверь. Ты сначала дергаешь ручку (это действие), а потом проверяешь, открылась ли дверь (это условие). Если не открылась, ты дергаешь снова.
Может быть сперва будет непонятно, но не бойся, просто попрактикуйся.
// Пример: "Делай хотя бы раз, а потом проверь"
int number = 0;
do { // Этот блок кода выполнится хотя бы один раз, // даже если условие в 'while' сразу окажется ложным. print("Текущее число: " + number); number++; // Увеличиваем число
} while (number < 0); // Условие: повторять, пока число меньше 0 (это условие сразу false после первого шага)
print("Цикл завершен. Число стало: " + number);
// Результат: "Текущее число: 0", "Цикл завершен. Число стало: 1"
В этом примере, даже если number
изначально не удовлетворяет условию while
, блок do
выполнится один раз, увеличив number
до 1
. После этого условие number < 0
(то есть 1 < 0
) станет false
, и цикл завершится.
Циклы - это фундаментальный инструмент, который позволяет автоматизировать повторяющиеся задачи и делать твои программы гораздо более мощными и эффективными
Функции и основы работы с ними
Представь, что ты строишь дом. Ты можешь каждый раз заново объяснять, как замешивать бетон, как класть кирпичи, как устанавливать окна. Но гораздо удобнее создать "инструкции" для каждого из этих действий: "Инструкция по замешиванию бетона", "Инструкция по кладке кирпичей" и так далее. И когда тебе нужно замешать бетон, ты просто говоришь: "Прочитай 'Инструкцию по замешиванию бетона' и выполни ее”
В программировании такие "инструкции" называются функциями.
Функция - это блок кода, который выполняет определенную задачу и имеет свое уникальное имя. Ты можешь написать этот блок кода один раз, а потом использовать (вызывать) его много раз в разных частях своей программы, просто обращаясь к нему по имени.
Зачем нужны функции?
Повторное использование кода: Если тебе нужно выполнить одно и то же действие в разных местах программы, ты просто пишешь функцию один раз и вызываешь её, когда нужно. Это намного лучше, чем копировать и вставлять один и тот же код.
Организация кода: Функции помогают разбить большую, сложную программу на маленькие, управляемые части. Каждая функция отвечает за свою конкретную задачу, что делает код более понятным и легким для чтения.
Удобство отладки (Легче найти ошибку): Если что-то не работает, ты знаешь, что проблема, скорее всего, в той функции, которая выполняет эту задачу. Не нужно пересматривать весь код.
Как выглядит функция? (Базовая структура)
У функции обычно есть:
Тип возвращаемого значения: Что функция "отдаст" после своей работы (например, число, текст, или ничего). Если ничего не отдает, пишут
void
.Имя: Уникальное название, по которому ты будешь вызывать функцию.(Пиши имя функции в стиле camelCase)
Параметры или аргументы (входные данные): Информация, которую ты можешь "передать" в функцию, чтобы она с ней работала (как ингредиенты(аргументы) для рецепта). Они указываются в круглых скобках
()
. Если параметров нет, скобки остаются пустыми.Тело функции: Сам блок кода, который выполняет функцию. Он заключается в фигурные скобки
{}
.
Вот как это выглядит:
// Функция, которая ничего не принимает и ничего не возвращает
void sayHello() { print('Привет, мир!');
}
// Функция, которая принимает имя и ничего не возвращает
void greetUser(String name) { print('Привет, ' + name + '!');
}
// Функция, которая принимает два числа и возвращает их сумму
int addNumbers(int a, int b) { int sum = a + b; return sum; // Возвращаем результат и обязательно напиши вместо void -> int так как функция возвращает(return) int значение
}
Как вызвать функцию?
Чтобы заставить функцию работать, нужно просто написать её имя и, если нужно, передать ей параметры(аргументы) в круглых скобках.
// Вызываем функцию sayHello
sayHello(); // Выведет: Привет, мир!
// Вызываем функцию greetUser и передаем ей имя
greetUser('Алексей'); // Выведет: Привет, Алексей!
greetUser('Мария'); // Выведет: Привет, Мария!
// Вызываем функцию addNumbers и сохраняем результат
int result = addNumbers(5, 3); // result будет равно 8
print('Сумма: ' + result.toString()); // Выведет: Сумма: 8
// Обрати внимание что я использовал метод .toString() без него была бы ошибка. Он просто переводит "что-то" в строку
int anotherSum = addNumbers(10, 20); // anotherSum будет равно 30
print('Другая сумма: ' + anotherSum.toString()); // Выведет: Другая сумма: 30
Функции - это один из самых важных и мощных инструментов в программировании. Они делают твой код чистым, организованным и легким для управления. Как только ты освоишься с ними, ты увидишь, насколько проще станет писать программы.
Немного про null safety(Безопасность от null)
Для начала стоит разобраться что такое вообще null
. Null - это пустота, это НИЧЕГО. Это не пустая строка или что-то еще. Это вообще пустота и ничего.
Теперь представь, что у тебя есть переменная. Иногда эта переменная может быть пустой (в программировании это называется null
).
Проблема: Если ты попытаешься взять что-то из пустой переменной, которой нет (то есть, обратиться к null
), твоя программа может "сломаться" или выдать ошибку, которую называют "ошибка null
" (NullPointerException
или Null Reference Exception
).
Если привести пример то это как пытаться выпить из пустого стакана – ничего не получится, так как в стакан полностью пуст.
Как работает null?
По умолчанию: переменная НЕ может быть пустой!
Если ты просто объявляешь переменную, как мы делали раньше, и не присвоишь ей значение, то выдаст ошибку, так как она обязательно должна что-то хранить и не может быть null
.
String name = 'Иван'; // Здесь всегда будет текст. Так как name НЕ может быть null.
int age = 30; // Здесь всегда будет число. Так как age НЕ может быть null.
String anotherName; // Ошибка! Эта переменная не может быть null, так как ей не присвоено значение.
Если переменная МОЖЕТ быть пустой: Тогда добавь перед ней знак ?
Если ты хочешь, чтобы переменная могла хранить null
(быть пустой), ты должен явно это указать, добавив знак вопроса ?
после типа данных.
String? name = null; // name МОЖЕТ быть null.
int? value = null; // value МОЖЕТ быть null.
Теперь компилятор знает, что name
или value
могут быть пустыми, и будет требовать от тебя осторожности при работе с ними.
Как безопасно работать с "пустыми" переменными?
Если у тебя есть переменная, которая может быть null
(со знаком ?
), ты не можешь просто так использовать её, как обычную. Тебе нужно сначала проверить, не пуста ли она:
String? userName = null; // Предположим, пользователь не ввел имя
// Проверяем, не пуста ли коробочка (не null ли userName)
if (userName != null) { // Если userName НЕ равно null, то можно вывести сообщение print('Привет, ' + userName + '!');
} else { // Если userName равно null(userName == null), то выводим другое сообщение print('Привет, Гость!');
}
Почему это хорошо для тебя, новичка?
Null Safety
(Null безопасность) - это твой друг! Оно помогает тебе:
Избегать ошибок: Компилятор заранее предупредит тебя, если ты попытаешься использовать переменную, которая может быть
null
, без проверки. Это значит, что твоя программа будет реже "падать" с неожиданными ошибками в рантайме.Писать более надежный код: Твой код становится более предсказуемым, потому что ты всегда знаешь, может ли переменная быть пустой или нет.
Лучше понимать код: Когда ты видишь
?
после типа, ты сразу понимаешь: "Ага, эта переменная может бытьnull
, нужно быть осторожным!"
Поэтому, Null Safety
- это не усложнение, а мощный инструмент, который делает твою программу безопаснее и помогает тебе писать более качественный код с самого начала
В заключение могу сказать что я бы мог рассказать еще про isolates, словари, списки, сеты, обработку ошибок, тернарные операторы и тд. Но это будет в следующей публикации. И надеюсь что я понятно всё объяснил и жду от вас фидбека