Базовые концепции программирования
Дата создания: 2010-10-02 02:33:36
Последний раз редактировалось: 2012-02-08 04:48:48
|
Данный урок является одним из вводных к основному курсу, где мы будем изучать язык программирования C++. Все языки программирования имеют много общего, поэтому имеет смысл сразу выделить это общее, так сказать, базовые понятия (концепции/принципы) программирования или основы программирования.
Основная работа программиста - перевести задачи, данные на обычном языке, в код, на каком-либо языке программирования. Любой язык программирования гораздо строже повседневного языка. В нём нет места избыточности и двусмысленностям.
Прежде чем мы начнём писать настоящие программы на C++, мы познакомимся с концепциями, характерными для всех языков программирования.
В этом уроке мы узнаем, что же доступно программисту во всех языках программирования.
Основы программирования
Начнём с самых основ. Как вам, наверное, известно, сайт посвящён созданию игр. Игры - это некоторая разновидность программ. Что же такое "программы"? Любая программа - это последовательность команд, выполнение которых приводит к какому-то результату. Результат может быть самым разным, в зависимости от задачи программы. Рассмотрим несколько примеров: результатом работы компьютерной игры будет создание целого виртуального мира на экране монитора, результатом работы программы для микроволновки станет подогретая вчерашняя курица, а результатом работы программы для баллистической ракеты будет тепло, которое согреет наших друзей с далёкого североамериканского континента.
Как нетрудно догадаться, команды (или инструкции), из которых состоит любая программа, должен кто-то выполнять. Этим занимаются процессоры. В разных устройствах используется разные по мощности процессоры. Например, в персональных компьютерах процессоры гораздо мощнее, чем в телефонах.
Процессор - сердце любого цифрового устройства. Что значит слово "цифровой"? Практически вся электроника, создаваемая человечеством в настоящее время, является цифровой. Слово "цифровой" означает, что команды процессору данного устройства являются числами. Мы ещё вернёмся к обсуждению цифровых устройств, когда накопим знаний, пока что пусть будет такое определение.
Так как команды процессору являются числами, то любая программа с точки зрения процессора - это последовательность чисел. Человек не сможет работать с такими программами. Ну, раньше могли, пока программы были очень маленькими и простыми.
Собственно, чтобы людям было проще создавать программы для электронных устройств, и были придуманы языки программирования. Программа, написанная на каком-либо языке программирования, называется исходным кодом программы. После того как исходный код полностью написан, его нужно перевести на язык процессоров (числовые команды). Процесс перевода исходного кода в выполняемую программу называется транслированием. Бывают два вида трансляторов: компиляторы и интерпретаторы. Первое время мы будем изучать компилируемый язык программирования (C++), а позднее - интерпретируемый (Python).
Теперь давайте подведём промежуточный итог: при постановке задачи, написать программу для какого-либо устройства, используется определённый язык программирования. На нём пишется исходный код программы. Затем этот исходный код с помощью транслятора переводится на язык понятный процессору (последовательность чисел). В результате получается программа, которую можно выполнить на цифровом устройстве.
Тут есть один небольшой нюанс. Для программы может существовать один исходный код, но несколько выполняемых программ. Пример: допустим, мы написали компьютерную игру на языке C++. Но при этом игра должна запускаться и на персональном компьютере и на приставке Sony Playstation 3 (или XBox 360, кому что больше нравится). Исходный код программы нужно транслировать два раза: и для PC и для PS3, так как процессоры этих двух устройств имеют разный набор команд. Т.е. исходный код у нас будет один, а выполняемых программ - две.
Отсюда следует интересный вывод: программа, оттранслированная для одного процессора, не будет работать на другом процессоре. Но если есть исходный код этой программы, то её можно оттранслировать и вуаля, теперь она выполняется на устройстве с другим процессором.
В предыдущем примере я немного упростил ситуацию. На самом деля для PC и для PS3 нужен немного разный исходный код. Если программа написана правильно, то исходный код нужно будет изменить совсем чуть-чуть (по сравнению с общим объёмом программы). А если неправильно? То быстрее будет переписать программу заново. Вам станет понятно, что я имею в виду, когда вы изучите дальнейшие уроки.
В вводной части я сделаю ещё одно замечание касательно алгоритмов. В школьной/вузовской программе понятие алгоритма трактуется довольно широко. Алгоритм - это инструкции для выполнения любой задачи (там что-то ещё есть в определении). При этом приводятся дурацкие примеры, о том, как дойти до работы или сварить яйцо.
Что я буду подразумевать под алгоритмами? На мой взгляд правильное определение этого термина должно выглядеть так: алгоритм - это определённый способ выполнения какой-либо задачи. При этом понятие алгоритма нужно неразрывно связывать со структурами данных. Для алгоритмов и структур данных на сайте есть отдельный раздел. Примером алгоритма может служить поиск пути в компьютерной игре. С алгоритмами мы будем знакомиться позже.
Если вы, в отличие от меня, хорошо учились в школе/ВУЗе, то могли заметить, что я довольно вольно трактую понятие алгоритма. Вообще, если можно так выразиться, я сторонник достаточно вольного отношения к знаниям. На мой взгляд, строгим терминам место в школьной/вузовской программе. В своих уроках я использую упрощённые определения и объяснения работы различных механизмов. В уроках вы не встретите ни одного доказательства теорем, а сами теоремы сформулированы довольно вольно. В силу этого мои уроки являются эдаким популистским изложением академических знаний. Пусть простят меня за это учителя/преподаватели/специалисты. При всём при этом полный цикл уроков предназначен на практическое применение знаний. В результате обоих этих факторов мои уроки становятся полезны (я надеюсь на это) широкому кругу читателей.
Теперь, когда мы разобрались с базовыми терминами, давайте рассмотрим "базовые концепции" или "составные части" всех языков программирования.
Для примера приведу пример из реальной жизни. В большинстве европейских языков в утвердительном предложении сначала ставится подлежащее, а потом сказуемое. При этом во всех этих языках текст делится на предложения. Вот подобные общие особенности мы сейчас и рассмотрим, только для языков программирования.
Информация и работа с ней в цифровых устройствах
В любой программе идёт обработка какой-то информации. Т.е. процессор выполняет команды и при этом работает с какой-то информацией. Информация - это то, что имеет какой-то смысл. Во всех последующих уроках наряду со словом "информация", я буду использовать слово "данные". Будем считать это примерно одним и тем же. Рассмотрим примеры информации/данных в компьютерных играх: музыкальная композиция, картинка, число, хранящее количество какого-то ресурса игрока.
Обратите внимание, что примерами информации служат не только текст и числа, но даже музыкальные композиции. И ведь действительно, музыкальные композиции - это не случайный набор звуков. С помощью музыки можно, например, передать настроение.
Как я написал выше, любая программа должна обрабатывать информацию. Например, количество ресурсов должно изменяться в зависимости от действий игрока, картинки должны выводиться на экран, а музыкальное сопровождение должно меняться в зависимости от игровой ситуации.
Для начала давайте ознакомимся, каким образом информация хранится в цифровых устройствах. Несмотря на то, что во всех уроках речь будет идти только о персональных компьютерах на архитектуре x86 и под управлением операционной системы Windows (XP, Vista, Seven и более ранних), информация из следующих разделов верна и для других электронных устройств. Во всех цифровых устройствах данные хранятся, в общем-то, похожим образом.
Представление данных в цифровом виде
Так как процессоры понимают только числа, то и любую информацию используемую в программе нужно представить в цифровом виде. Да, это не шутка! Даже картинки и музыку можно представить с помощью чисел. Представление информации в цифровом виде называется кодированием. В кодировании не нуждаются только числовые данные, например, количество ресурсов у игрока. Все остальные данные нужно кодировать. В будущих уроках мы узнаем, как кодируется текст в Windows, как кодируются картинки bmp, jpg, png, как кодируются трёхмерные модели формата x, как кодируются звуковые файлы wav и ogg. А пока что просто поверьте, что подобную информацию можно представить числами.
Любую информацию нужно где-то хранить. Для этого предназначены различные устройства памяти: жёсткие диски (там хранятся данные большую часть времени), оперативная память (туда помещаются данные, когда программа выполняется), флешки, оптические диски, различные карты памяти электронных устройств.
В устройствах памяти информация хранится определённым образом. Дело в том, что процессоры могут работать не просто с числами, а только с числами в двоичной системе счисления. Число в двоичной системе счисления может принимать только два значения: ноль и единицу. "Ну вот, становится совсем весело!" - скажет внимательный читатель - "Сначала, оказывается, что музыку можно хранить в виде чисел. Так этого мало, числа должны быть в какой-то двоичной системе счисления. Уж не дуришь ли ты меня, дорогой автор? Может компьютеры работают с помощью магии?" На это я могу сказать только одно: "Истину вам говорю...". В следующих уроках мы познакомимся с двоичной системой более подробно и научимся переводить числа из десятичной системы (той, которой мы обычно пользуемся) в двоичную. На данный момент вы должны понимать, что любая программа на языке процессора выглядит вот так:
010101110101101010101010101011110110000011010111100111101011010111011010111011010111011 101010111101100000110101111001111010110101110110101110110101110111100010101110101101000 101111001111010110101110110101110110101110111100010101110101101000110001011010101000111 110101101011101101011101101011101111000101011101011010001100010110101010001110010111100 101110110101110110101110111100010101110101101000110001011010101000111001011110010111001 011010111100111101011010111011010111011010111011010101110101101010101010101011110110000
Здесь показаны и команды, и информация, которую они должны обработать.
Конечно же, для разных программ последовательность нулей и единиц будет разной. Реальная ли это программа? Нет, это я напечатал для примера.
Что может быть в такой последовательности? Это могут быть какие-то данные и какие-то команды процессору (сложить два числа, например).
И ещё один важный момент: выше продемонстрирован очень маленький отрывок "программы". В реальных современных программах таких чисел миллиарды.
Двоичное число - это минимальная единица информации. Такое число может принимать только два значения: ноль и единицу. Двоичное число принято называть битом. Это название происходит от английского сокращения bit - binary digit (произносится как бинэйри диджит), binary - двоичный, digit - цифра.
Так как бит - слишком маленькая единица информация, то во всех современных электронных устройствах память группируется по восемь бит. Информация в восемь бит называется байтом (byte). Почему именно восемь? Так исторически сложилось. Отмечу только, что связано это с кодированием текста в ASCII (узнаем об этом в одном из будущих уроков).
Обратите внимание, что 8 - это 23. Вообще, двойка имеет огромное значение во всей цифровой электронике. Думаю, понятно почему. Помимо двойки огромное значение имеют степени двойки, особенно восьмая (количество значений в одном байте) и 32-ая (основа архитектуры всех современных персональных компьютеров).
Давайте рассмотрим примеры, с помощью которых можно ощутить различные объёмы информации.
В одном бите может храниться только два значения: 0, 1. На практике такую малую единицу использовать нельзя.
В одном байте уже можно разместить 256 значений (28). В байт без проблем можно закодировать все символы русского алфавита.
Два байта - 216 = 65536 значений. С помощью двух байт можно закодировать все буквы и иероглифы всех письменностей. Именно два байта используются для кодирования текста в Windows.
Три байта - 224 - 16 777 216 (более 16 миллионов значений). Основа для представления цвета в современных компьютерах - true color.
Четыре байта - 232 - 4 294 967 296 (более четырёх миллиардов значений). Число, являющееся основой для всех нынешних персональных компьютеров.
Сразу хочу обратить ваше внимание на следующий факт. При измерении объёма памяти используется степень двойки:
1 Килобайт - это не 1000 байт, а 1024 - 210.
1 Мегабайт - 220 - 1 048 576 байт или 1024 килобайта.
1 Гигабайт - 230 - 1 073 741 824 байта или 1024 мегабайта.
1 Терабайт - 240 - 1 099 511 627 776 или 1024 гигабайта.
Как вы знаете, многие современные программы имеют размер не в один гигабайт (особенно игры). Даже если брать программу размером всего лишь в один гигабайт, то это будет более восьми миллиардов нулей и единиц (количество байт нужно умножить на восемь). Сравните с тем кусочком, который я привёл выше.
Все байты в устройствах памяти нумеруются - получают адреса. С помощью адреса к этому байту может обратиться процессор (чтобы получить информацию из этого байта).
Адрес в современных компьютерах - 32-битное число (заметьте, степень двойки). Для удобства восприятия человеком адреса записываются в шестнадцатеричной системе счисления. С шестнадцатеричной системой счисления мы будем знакомиться позже. Пока лишь пара замечаний. в этой системе 16 цифр. Так как глупые арабы придумали только 10 цифр, то числа от 10 до 15 записываются латинскими буквами. Все цифры шестнадцатеричной системы выглядят вот так: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. Перед цифрами в шестнадцатеричной системе принято ставить 0x (ноль икс). Адреса в такой форме используются, чтобы сократить размер числа. Допустим, в нашей программе мы работаем с адресом 4 000 000 000 (4 миллиарда, 10 цифр), в шестнадцатеричной системе этот адрес будет выглядеть вот так 0xEE6B2800 (8 цифр). И в Window, и в Linux есть калькулятор, в котором можно переводить числа из одной системы счисления в другую.
Теперь, когда мы рассмотрели теоретический вопрос хранения информации в цифровом виде, давайте перейдём в более практическую плоскость и рассмотрим, какие средства для хранения информации есть в языках программирования.
Переменные
Для начала мы рассмотрим вопрос хранения малых объёмов информации - простых чисел.
Во всех языках программирования для хранения чисел используются переменные. Переменная - это представление небольшого участка памяти в программе. При этом в переменной можно хранить только одно значение.
Переменные могут быть разного размера - один байт, два байта, четыре байта, восемь байт. Обычно, хватает переменных размером в четыре байта. В такой переменной можно хранить более четырёх миллиардов значений. Этого достаточно для хранения большинства, как целых, так и вещественных чисел. Но иногда требуется повышенная точность, тогда имеет смысл использовать переменные размером в восемь байт.
По-английски переменная - variable (читается как вэрйэбл).
У любой переменной есть имя (идентификатор). Переменные предназначены для того, чтобы программист не обращался к памяти с помощью адресов.
Допустим, в нашем языке программирования нет переменных и программисту нужно обращаться к памяти с помощью адресов. Есть у нас, например, число, которое хранится по адресу 0x00fc2300. С этим числом нам нужно в программе совершать какие-нибудь действия:
Вывести число, хранящееся по адресу 0x00fc2300 на экран Увеличить значение числа, хранящегося по адресу 0x00fc2300 на 10 Вывести число, хранящееся по адресу 0x00fc2300 на экран
Как видите, использование адресов не слишком удобно. Если использовать переменную, то это будет выглядеть вот так:
Вывести var на экран Увеличить var на 10 Вывести var на экран
var - имя переменной. Эта переменная привязана к адресу 0x00fc2300. Теперь в любом месте, где нам нужно обратиться к значению по адресу 0x00fc2300, мы пишем имя переменной. Всё остальное за нас сделает транслятор.
Переменные - это одна из самых главных вещей в любом языке программирования, поэтому мы на этой теме остановимся поподробнее.
Переменную можно представить как контейнер или ячейку определённого размера. Программист указывает имя (идентификатор) и размер переменной, всё остальное процессор сделает сам. Рассмотрим пример, в котором поэкспериментируем с небольшим участком памяти:
0x00000001 { 0 } 0x00000002 { 0 } 0x00000003 { 0 } 0x00000004 { 0 } 0x00000005 { 0 } 0x00000006 { 0 } 0x00000007 { 0 } 0x00000008 { 0 } 0x00000009 { 0 } 0x0000000a { 0 } 0x0000000b { 0 } 0x0000000c { 0 } 0x0000000d { 0 } 0x0000000e { 0 } 0x0000000f { 0 } 0x00000010 { 0 } 0x00000011 { 0 }
Здесь мы взяли участок памяти из 17 байт. Слева показаны адреса, справа, в фигурных скобках, значения, хранящиеся по этим адресам. Будем считать, что фигурные скобки обозначают участок памяти в один байт. Здесь я допустил, что процессор по умолчанию хранит в памяти нули (на самом деле это может быть не так).
Допустим, мы хотим использовать часть памяти из этого участка для хранения значения ресурсов в нашей игре. Для этого нам нужно создать переменную. Пусть игрок может накопить несколько миллионов единиц этого гипотетического ресурса. Как я писал выше, компьютер может создать переменную только определённого размера: 1, 2, 4, 8 байт (степени двойки, какой сюрприз). Т.е. современные процессоры в силу своих конструктивных особенностей не могут создать переменную размеров в три байта. 2 байта для нашей переменной слишком мало (всего 60 тысяч значений). Поэтому в нашей программе мы попросим процессор создать переменную размером в четыре байта. Имя переменной пусть будет var4. Выглядеть это будет примерно вот так:
Дорогой процессор, создай, пожалуйста, var4 размером в четыре байтаЕсли процессор выполнит наше желание (а куда он денется с подводной лодки-то?), то четыре байта в нашей памяти станут занятыми. Заметьте, мы не можем указать, в каком именно месте создавать переменную. Адрес переменной будет выбран автоматически. Пусть этот адрес будет 0x00000009. При этом процессор "запомнит", что следующие три адреса тоже принадлежат var4, и не будет там создавать новых переменных. Теперь наша память выглядит вот так:
0x00000001 { 0 } 0x00000002 { 0 } 0x00000003 { 0 } 0x00000004 { 0 } 0x00000005 { 0 } 0x00000006 { 0 } 0x00000007 { 0 } 0x00000008 { 0 } 0x00000009 { 0 } var4 { 0 } { 0 } { 0 } 0x0000000d { 0 } 0x0000000e { 0 } 0x0000000f { 0 } 0x00000010 { 0 } 0x00000011 { 0 }
Здесь я показал, что из памяти как бы исчезают адреса 0x0000000a, 0x0000000b, 0x0000000c - они перестают быть доступными для создания новых переменных.
Весь этот процесс правильно называется выделением памяти под переменную. Но можно и по-простецки - создание переменной.
Итак, переменная var4 занимает четыре байта. Адресом этой переменной считается первый адрес - 0x00000009. Именно по этому адресу к var4 обращается процессор. На нашем участке показаны значения байтов. Давайте для простоты покажем на этом участке конечное значение:
0x00000001 { 0 } 0x00000002 { 0 } 0x00000003 { 0 } 0x00000004 { 0 } 0x00000005 { 0 } 0x00000006 { 0 } 0x00000007 { 0 } 0x00000008 { 0 } 0x00000009 | 0 | var4 0x0000000d { 0 } 0x0000000e { 0 } 0x0000000f { 0 } 0x00000010 { 0 } 0x00000011 { 0 }
Здесь я оставил только адреса, к которым может напрямую обратиться процессор. Плюс, число, состоящее из четырёх байт я поместил в прямые скобки, чтобы было видно, что в такой ячейке можно хранить значение из четырёх байт.
Пока что в памяти занятой var4 ничего нет - мы только выделили под переменную память и ничего туда ещё не поместили. Пусть начальным значением ресурсов будет 13 миллионов. Тогда наш участок памяти будет выглядеть так:
0x00000001 { 0 } 0x00000002 { 0 } 0x00000003 { 0 } 0x00000004 { 0 } 0x00000005 { 0 } 0x00000006 { 0 } 0x00000007 { 0 } 0x00000008 { 0 } 0x00000009 | 13 000 000 | var4 0x0000000d { 0 } 0x0000000e { 0 } 0x0000000f { 0 } 0x00000010 { 0 } 0x00000011 { 0 }
Когда в нашей программе мы будем использовать переменную var4, то вместо неё процессор подставит то значение, которое хранится по адресу var4. При этом процессор прекрасно знает сколько места занимает эта переменная.
Это мы разобрали хранение небольших единичных кусочков информации: числа, отдельные буквы. Теперь давайте узнаем, какие средства есть в языках программирования для хранения больших объёмов информации: текста, картинок, видео.
Массивы
Массивы позволяют хранить несколько переменных одного размера рядом друг с другом. Рассмотрим пример. В программе "Арканоид", в качестве фона, я использовал картинку размером 500*500 пикселей (точек). Каждая точка занимает четыре байта памяти. Итого получается, что на хранение всей картинки нужно 500*500*4 = 1 000 000 байт. В программе, при загрузке картинки из файла создаётся массив размером в 250 тысяч элементов, каждый из которых - переменная размером в четыре байта. Т.е. при запуске арканоида процессор выделяет почти 1 мегабайт памяти только для фонового изображения.
У любого массива есть имя (или идентификатор). Назовём наш массив, в котором хранится картинка, array (array - массив, читается как эррэй).
Давайте посмотрим, как массив расположится в памяти. Адресом массива является адрес первого элемента. Пусть адресом массива array будет 0x00000003:
0x00000000 { 0 } 0x00000001 { 0 } 0x00000002 { 0 } 0x00000003 | ? | array 0x00000007 | ? | ... 0x000f423f | ? | 0x000f4243 | ? |
Знаками вопроса я показал, что в этих элементах массива содержится какое-то конкретное значение, но нам оно не известно.
Массив array может получить доступ к любому своему элементу. Для этого используется сдвиг по адресам. Допустим, мы хотим получить доступ ко второму элементу массива, это будет выглядеть вот так: array + 1. Это выражение позволяет получить доступ к адресу 0x00000007. Т.е. запись array + 1 означает, что нужно взять элемент, отстоящий на единицу от начала массива (как раз второй элемент).
Обратите внимание, что сдвиги осуществляются не по байтам, а по элементам массива. Т.е. если у нас будет массив, состоящий из переменных размером в два байта, то запись array + 1 будет означать сдвиг на два байта.
Для удобства почти во всех языках программирования используются индексы, которые записываются в квадратных скобках после имени массива:
array[индекс] array[1] - второй элемент array[249999] - последний элемент
Индекс - это и есть значение, на которое нужно сдвинуть адрес первого элемента. Просто во многих языках нельзя напрямую написать array + 1, а используются квадратные скобки - array[1]. Но фактически процессор всегда выполняет сложение начального адреса массива с номером элемента (умноженного на размер одного элемента массива), к которому нужно получить доступ.
Мы рассмотрели все базовые возможности языков программирования для хранения информации: отдельные переменные и массивы. Есть более сложные способы хранения данных (структуры, классы), но они так или иначе используют отдельные переменные или массивы.
Теперь давайте посмотрим, какие же средства по обработке данных процессором доступны в языках программирования.
Управление последовательностью выполнения команд в языках программирования
Сразу введём новый термин. Как мы узнали выше, любая программа состоит из команд (или инструкций) процессору. Командой может быть: сложение двух чисел, передача процессору значения какой-нибудь переменной или сохранение нового значения переменной в память, сравнение двух чисел и др. Как видим, команды довольно примитивные. Но именно из таких команд и состоят программы любой сложности. В языках программирования не используются команды процессору напрямую (ну, если только в ассемблере). Вместо этого используются операторы.
Оператор - это минимальная единица кода в языке программирования. Можно сказать, что оператор - это предложение в языках программирования.
Обычно, оператор объединяет в себе несколько команд процессору. Допустим, мы хотим присвоить участку памяти новое значение посредством прибавления к текущему значению какого-нибудь числа. В коде это будет выглядеть так:
var = var + 5
Это типичный оператор. Здесь в участок памяти, представленный переменной var, помещается увеличенное на пять текущее значение. Думаю, здесь всё понятно. Но этот оператор при трансляции превратится в несколько команд процессора: нужно вытащить из памяти значение var, затем нужно увеличить значение var, после этого нужно скопировать получившееся значение обратно в память.
Операторы представляют команды процессора в языке программирования. Т.е. операторы выполняют какие-то действия над данными.
Последовательное выполнение операторов
Во всех языках программирования операторы выполняются последовательно один за другим. Посмотрим на такой код:
Прибавить к var пять Создать var Вывести var на экранЗдесь три оператора. Но они расположены не в том порядке, потому как, прежде чем использовать переменную var (прибавлять к ней 5), её сначала нужно создать. В исходном коде на любом языке программировании операторы выполняются сверху вниз - сначала процессором будут выполнены верхние операторы, затем нижние.
Операторы выполняются один за другим. Процессор не может одновременно выполнять два (и более) оператора. Давайте посмотрим на ещё один пример:
Создать переменную var1 Присвоить var1 ноль Создать переменную var2 Присвоить var2 5 Создать переменную var3 Присвоить var1 значение var2 Присвоить var3 сумму var1 и var2
В результате выполнения этой программы, переменная var3 будет содержать значение 10. Нельзя поставить последний оператор в начало кода (в это время ещё не создано ни одной переменной), или на место четвёртого оператора (ещё не создана переменная var3), или на место пятого оператора (тогда поменяется итоговое значение var3, оно будет равно пяти). У каждого оператора в любой программе есть своё место, и это место определяет программист.
Ветвления
В любом языке программирования ветвления занимают очень важную роль. В нормальной ситуации операторы выполняются последовательно. Ветвления позволяют выполнить какую-то одну часть кода (из двух и более) на основе проверки условия.
Рассмотрим пример. Допустим, мы пишем искусственный интеллект для бота в нашей стрелялке. Мы должны сформировать поведение бота. Одно из правил будет следующим: если здоровье меньше 20%, то нужно найти аптечку. В коде это будет выглядеть так:
Если (здоровье < 20) Искать аптечку Другой код
Здесь у нас есть простое условие, выполнение которого запускает алгоритм поиска аптечки. Если условие не выполняется (здоровья ещё много), то поиск аптечки будет пропущен, а сразу начнёт выполняться "другой код".
Теперь давайте усложним задачу. Пусть бот будет реагировать на присутствие противника:
Если (рядом противник) { Атаковать противника } Во всех остальных случаях { Если (здоровье < 20) { Искать аптечку } } Другой код
Здесь две ветки кода, которые зависят от одного условия. Чтобы код легче читался, каждую из ветвей кода я выделил фигурными скобками. Т.е. то, что находится внутри пары фигурных скобок { }, относится к одной ветви ветвления.
Заметьте, что в любом случае будет выполнена одна из ветвей кода: если бот обнаружил рядом противника, то он вступает с ним в схватку. Если противника рядом нет, то будет проверено состояние здоровья.
Также мы видим здесь два вложенных ветвления - когда одно ветвления находится внутри другого ветвления. Сразу скажу, что желательно не допускать глубокой вложенности ветвлений друг в друга (не больше 3-4):
Если (условие) { Если (другое условие) { Если (ещё одно условие) { Если (ещё одно условие) { Если (ещё одно условие) { Код } } } } }
За таким кодом уже будет довольно сложно уследить. В своих программах используйте не больше 3-4 вложений.
Давайте закончим работу над искусственным интеллектом, чтобы бот правильно реагировал на все возможные ситуации:
Если (рядом противник) { Если (здоровье < 20) { Убегать от противника } В противном случае { Атаковать противника } } Во всех остальных случаях { Если (здоровье < 20) { Искать аптечку } Если это не так, то проверить (патронов < 20) { Искать патроны } Во всех остальных случаях { Искать противника } }
Здесь у нас уже три ветвления. Одно внешнее, в котором две ветки, и два вложенных. В одном из вложенных ветвлений целых три ветви (возможно любое количество ветвей). Обращаю ваше внимание, что для любого из трёх ветвлений будет выполнена одна из его ветвей - так построены ветвления.
В этом примере я продемонстрировал основные варианты ветвлений. Больше не будет (возможны только различные комбинации).
Можно было бы написать ветвления, в которых необязательно выполняются все ветви. Примером такого ветвления может служить первый пример:
Если (здоровье < 20) Искать аптечку Другой код
У этого ветвления только одна ветвь. И если условие неверно, то сразу начнёт исполняться код, идущий после ветвления ("другой код"). Ещё одним примером ветвления, в котором возможно невыполнение ни одной из ветвей, будет служить такое:
Если (здоровье < 20) { Искать аптечку } Если это не так, то проверить (патронов < 20) { Искать патроны } Другой код
Если у бота достаточно и здоровья, и патронов, то сразу начнётся выполнение "Другого кода".
На этом по ветвлениям всё.
Циклы
Последней темой сегодняшнего урока станут циклы. Циклы применяются если необходимо выполнить много одинаковых действий. Вернёмся к примеру с картинкой. В нашем примере картинка состоит из 250 тысяч точек (пикселей). И нам нужно вывести картинку на экран. Напоминаю, что идентификатор (имя) массива привязывается к адресу первого элемента в памяти. Если получать доступ к первому элементу в индексах, то это будет выглядеть вот так array[0] (сдвинуть адрес на ноль от начала массива) - мы получим первый элемент. Последний элемент - 249 999 (сдвинуть адрес на 249 999 элементов от первого), это последний - 250000-ый элемент.
Наша задача - вывести весь этот массив на экран. Мы будем выводить его по отдельным пикселям. Конечно, мы можем записать и вот так:
Вывести пиксель array[0] Вывести пиксель array[1] Вывести пиксель array[2] ... вывод остальных пикселей Вывести пиксель array[249999]
Но это ж рехнуться можно! Поэтому мы воспользуемся циклами. Нам потребуется создать вспомогательную переменную, которая во время выполнения цикла будет принимать разные значения: от нуля до 249999:
Создать переменную pixel размером в четыре байта Загрузить из памяти картинку в массив array pixel = 0 Цикл (pixel < 250000) { Вывести пиксель array[pixel] на экран Увеличить переменную pixel на единицу }
Теперь давайте разберём, что здесь происходит. В любом цикле есть условие проверки выполнения цикла. Если условие цикла выполняется, то выполняется и сам цикл (код внутри фигурных скобок). В нашем примере код цикла будет выполнен 250 000 раз. Когда цикл начнёт выполняться в 250 001 раз, переменная pixel будет равна 250 000 (она увеличивается каждый раз во время выполнения цикла), а соответственно условие цикла будет неистинным.
Конечно же, в коде можно использовать вложенные циклы, а также циклы, вложенные в ветвления, и ветвления, вложенные в циклы.
Заключение
На этом мы завершаем урок по основам программирования. Напоминаю, что материал данного урока остаётся верным для любого языка программирования: C/C++, Pascal/Delphi, Basic, PHP, Perl, Python, C#, Java и др.
В дальнейших уроках мы начнём знакомиться с языком программирования C++. Почему я выбрал C++ для своих уроков, я объяснял в аннотации к соответствующему разделу.