Давайте сначала рассмотрим языки программирования не в узкопрофессиональном, а в более широком плане. Другими словами, я предлагаю вам сначала ознакомиться с самыми общими категориями, так или иначе практически воплотившимися во всех современных языках программирования. Без их понимания невозможно приступить к изучению ни первого в вашей жизни, ни последующих конкретных языков. Более того, не овладев общими понятиями, нельзя эффективно продвигаться в решении конкретных задач, усваивать уже устоявшиеся, а также новые технологии программирования.
Все языки программирования включают в себя, по меньшей мере, следующие понятия:
- переменные и типы данных:
- массивы данных;
- функции;
- объекты;
- операторы;
- выражения.
Эти краеугольные составляющие могут воплощаться или, другими словами, поддерживаться различными языками по-разному. Далее мы попытаемся уяснить собственно базовые понятия, но прежде обратимся к аналогиям с естественными языками.
Естественные языки базируются на таких категориях, как имя существительное, имя прилагательное, глагол и т. п. Слова, которые мы встречаем в обычной живой речи, принадлежат к той или иной категории языка. Так, слово "стол" — имя существительное, "красный" — прилагательное, а "писать" — глагол.
Категории естественного языка воплощаются в речи, исполняя ту или иную роль члена предложения (выражения языка), например, подлежащего, сказуемого, определения, дополнения, обстоятельства. При этом исходные слова адаптируются согласно правилам построения предложений. Так, существительные и прилагательные склоняются по падежам, а глаголы спрягаются по временам. Вместе с тем слово одной и той же категории в предложении может выполнять различные функции. Например, существительное может фигурировать в предложении и как подлежащее (главный член предложения), и как дополнение (второстепенный член), в зависимости от структуры предложения. Итак, слова различных категорий языка, т. е. составляющих его базу, могут выполнять различные роли в предложении, или реализации словоупотребления. Как и в естественном языке, примерно то же самое происходит и в языках программирования.
Если вы новичок, то первое, на что вы должны обратить внимание при изучении языка программирования, — как реализуются на практике перечисленные понятия. Если вы уже знакомы с каким-нибудь языком, то при освоении нового следует лишь уточнить особенности реализации базовых понятий.
Переменные и типы данных.
Важнейшим в программировании является понятие переменной, с уяснения которого и следует начать. Оно изначально возникло в математике задолго до появления первых вычислительных устройств и программ для них. В математике под переменной понимают то, что может быть заменено конкретным значением. В уравнениях, известных вам еще со школы, фигурируют переменные, обозначаемые чаще всего символом х
. Разумеется, обозначать переменные можно и другими буквами или их последовательностями (например, s
, myvar
или сумма
). Вот типичный пример уравнения, встречающегося в математике: (x/2) — 2 = 0
.
Здесь х
— символ, вместо которого можно подставлять различные значения, но не совсем произвольные, а именно числа. При этом говорят также, что х
"пробегает" значения из множества чисел. Заметьте, что данная переменная не может принимать значения в виде строк, составленных из букв, а также дат (например, 16.12.2007) или других данных нечислового типа. Иначе говоря, переменная в математике — это символьное обозначение некоторого числового значения, но не любого, а лишь принадлежащего определенному множеству, например, всех чисел, или только действительных, или только целых, или каких-то других. Таким образом, переменная — это заместитель в некотором выражении (формуле) конкретного значения из наперед заданного множества или, как еще говорят, типа данных.
Итак, переменная может фигурировать в некотором математическом выражении. Что она там делает и каков смысл данного выражения с ее участием?
Переменная выполняет исключительно "представительские" функции. Она, так сказать, представляет некоторое, а лучше сказать, любое значение из заранее определенного множества, которое можно назвать типом значений.
Те, кто учился в школе, легко могут понять, что приведенное выше выражение констатирует тот факт, что X деленный на 2 и за вычетом двух равно нулю. Сам собой напрашивается вопрос: а каково конкретное значение переменной, которое обеспечивает выполнение данного равенства?
Столкнувшись с некоторым уравнением, вы сразу же формулируете некую математическую задачу, решение которой должны дать математики. Естественно, встреча с чем-то новым порождает попытку понять, к чему бы это. Если это математическое уравнение, то само собой речь пойдет о его решении, т. е. о вычислении тех значений переменной, при которых данное уравнение выполняется. Однако с точки зрения программирования дело совсем не в этом.
В математике, по крайней мере в школьной, обычно рассматривают всяческие уравнения и неравенства. При этом зачастую сложные и длинные выражения обозначают более компактными именами, т. е. вводят в обиход новые переменные. Этот прием позволяет в дальнейшем сократить математические выкладки, заменяя данное многосимвольное выражение более коротким. В математике в договоре о новом обозначении или, другими словами, о вводе новой переменной, обычно говорят что-то в следующем духе:
- "обозначим через
у
выражение(x/2) — 2
"; - "пусть
у
равно(x/2) — 2
".
Как в математике, так и в программах подобные предложения записывают кратко: у = (x/2) — 2
.
Если в математике данное выражение является краткой записью соглашения о введении нового обозначения или констатацией равенства значений левой и правой частей равенства, то в программировании это команда, требующая присвоить переменной у
значение выражения, расположенного справа от знака равенства. Чтобы эта команда могла быть выполнена компьютером. необходимо сначала вычислить значение выражения справа от знака равенства, и только потом присвоить полученное значение переменной.
В математике данное выражение обычно трактуется как утверждение о том, что левая часть равенства эквивалентна правой. Соответствующее предложение на естественном языке имеет изъявительное наклонение, т. е. это повествовательное предложение, утверждающее в данном случае факт равенства. В повелительном наклонении то же предложение можно сформулировать, например, так: "пусть (x/2) — 2 = 0
". В переводе на язык программирования выражение (x/2) — 2 = 0
следует понимать не как требование присвоить левой части равенства значение правой, а как утверждение о равенстве левой и правой частей, истинность которого еще необходимо проверить. Иначе говоря, с точки зрения языка программирования данная запись соответствует операции сравнения выражений слева и справа от знака равенства. Результатом этого может быть одно из двух: ИСТИНА (если левая и правая части оказались равными) или ЛОЖЬ (в противном случае). Таким образом, выражение (x/2) — 2 = 0
следует трактовать как "проверить, выполняется ли равенство (x/2) — 2 = 0
". Разумеется, такая проверка предполагает подстановку в его выражение конкретного значения вместо символа х
.
Таким образом знак равенства в выражениях языка может играть две совершенно различных роли. Во-первых, в простейших выражениях вида х = 5
он обозначает повеление присвоить переменной х некоторое значение (в данном случае 5). Во-вторых, в выражениях вида (x/2) — 2 = 0
тот же знак равенства следует трактовать как операцию сравнения того, что находится слева от него, с тем, что расположено справа.
Однако при изучении математики (вспомните школу!) с этим не возникало особых проблем. Именно потому, что один и тот же знак равенства может выполнять в математических выражениях различные роли, в языках программирования для каждой из этих ситуаций обычно предусматривают свой знак. Так например, для присваивания переменной некоторого значения обычно используют символ =
, а для сравнения значений двух выражений на предмет их равенства — двойной символ равенства ==
.
Например, выражение у = (x/2) — 2
в программе следует понимать как команду присвоить переменной у значение выражения (x/2) — 2
, а выражение у == (x/2) — 2
— как операцию сравнения значений у
и (x/2) — 2
. Таким образом снимается проблема неоднозначной интерпретации операций, которые должен произвести компьютер. Замечу попутно, что в некоторых языках для присваивания употребляют символ :=
, а для сравнения =
. Однако все это— особенности синтаксиса конкретных языков программирования, которые легко освоить с помощью справочников. Никогда синтаксис языка, каким бы вычурным он ни был, не создавал настоящих проблем для его изучения. Трудности возникают при словоупотреблении и формировании предложений. Человек, изучающий не первый в своей жизни язык программирования, легко и непринужденно усваивает его скучные нотации и спецификацию потому, что уже имеет некоторый опыт составления осмысленных предложений на искусственных языках.
А теперь в качестве тестового примера рассмотрим выражение вида х = х + 1
. С точки зрения математики данное уравнение не имеет решения, поскольку легко преобразуется к виду 0 = 1
, что, очевидно, никогда не выполняется (данное равенство ложно). А раз так, то оно нам и не интересно. Однако с точки зрения программирования подобная запись, означающая присвоение переменной х
значения выражения х + 1
, имеет смысл. Что же должен сделать компьютер, выполняя данное выражение?
Нетрудно догадаться, что к текущему (ранее заданному) значению переменной х следует прибавить единицу и полученный результат присвоить этой же переменной x
. Другими словами, мы можем в качестве нового значения переменной использовать результат каких-то преобразований ее предыдущего значения.
Итак, в математике переменная, обозначаемая каким-нибудь именем из одного или нескольких символов, представляет (обозначает) собой некоторое значение из заранее оговоренного множества допустимых величин. В языках программирования понятие переменной имеет аналогичный смысл — это контейнер для хранения данных. Данные, которые могут храниться в переменной, называют значениями этой переменной. Они могут быть весьма разнородными: числами, текстовыми строками, датами, массивами и др.
Данные группируют в множества, называемые типами данных. Простейшие примеры типов данных — числа и произвольные последовательности символов (строки), встречаются и другие, например, дата, время и т. д. Переменные классифицируют в зависимости от типа данных, которые они могут хранить. Так, например, если переменная предназначена для хранения чисел, то говорят, что она имеет числовой тип или, короче, что это числовая переменная.
Вспомним элементарную арифметическую задачу из сказки про Буратино: "Вы имеете три яблока и одно из них отдали мне. Сколько яблок у Вас осталось?"
В жизни, как только встречаемся с числами, мы обычно абстрагируемся от того, к чему они относятся, к яблокам, автомобилям или еще чему-то. Поскольку в данном случае речь идет об изъятии яблока, то мы считаем, что этой операции соответствует обыкновенное вычитание одного числа из другого. Таким образом, мы абстрагируемся от яблок, оставляя в поле зрения только их количества, а передачу яблок другому лицу интерпретируем как арифметическую операцию вычитания или прибавления. Это настолько естественно, что и программист должен рассуждать аналогично.
В рассматриваемом нами случае программист мог выполнить все необходимые интерпретации условия задачи в уме, а машине поручить лишь рутинную операцию арифметического вычитания над числами 3 и 1, а именно вычислить значение выражения 3 - 1. Компьютер, очевидно, выдал бы в результате число 2, а программист сказал нам: "останется два яблока". Однако возможен и иной ход событий. Если в компьютер ввести данные в другой форме: "3 яблока" и "1 яблоко", то что получится в результате попытки вычислить выражение "3 яблока" - "1 яблоко"? Очевидно, произойдет попытка выполнить арифметическую операцию не над числами, а над символьными строками, пусть даже и содержащими числа. Это, возможно, поставит компьютер в тупик.
Тупик будет более очевидным, если мы предложим компьютеру выполнить такую операцию: "Саша" — "Маша". Данные, участвующие в этой операции, не содержат чисел, а потому у нас нет никакого разумного основания интерпретировать действие, обозначенное символом "минус", как арифметическое вычитание. Что должно получиться в итоге? Каким должно быть слово, являющееся результатом данной операции?
Не исключено, что можно придумать некоторую особую интерпретацию этой операции. Например, пусть это будет буквосочетание, которое в качестве фрагмента есть в первом, но отсутствует во втором слове. Однако такое определение слишком специфично или, иначе говоря, не общезначимо, а потому нам проще объявить данное и подобные выражения лишенными смысла и запретить их использовать в конструкциях языка программирования.
В жизни мы имеем дело с различными данными, относя их к разным типам по той простой причине, что для одного типа данных имеют смысл и допустимы одни операции, а для другого — другие. То, что можно делать с числами, интерпретируя операции над ними в математике, нельзя выполнять с последовательностями произвольных символов, сохраняя при этом интерпретацию результата в той же математике. Наоборот, мы можем склеивать две последовательности символов или отсекать от них ненужные части, но результат подобных операций можно осмыслить, например, в редакторской деятельности, но только не в математике. Поэтому последовательности произвольных символов (строки) выделяют в отдельный тип данных.
Далее, дату можно представить в виде набора из трех чисел (число, месяц и год), которые обычно отделяют друг от друга в одной записи точками. Очевидно, что сложение двух дат едва ли можно понять как сложение двух чисел. Набор из нескольких чисел — более сложный объект, чем отдельное число. Однако прибавление к дате некоторого обычного числа вполне разумно интерпретировать как некую новую дату, следующую за исходной спустя заданное число дней. Вычисление этой даты, т. е. представление в виде набора из трех чисел не совсем тривиально (нужно знать, сколько дней в каждом месяце и году). Иначе говоря, операции над датами отличаются от простых арифметических операций над обычными числами, а потому даты также относят к особому типу данных.
В языках программирования существуют так называемые встроенные типы данных. Это общезначимые, наиболее часто употребляемые и достаточно отчетливо интерпретируемые всеми типы. Вместе с тем, современные языки программирования допускают задание программистом своих особенных типов данных. Например, вы можете определить тип данных НЕДЕЛЯ
, который содержит 7 элементов, представленных целыми числами (например, 1, 2,...,7) или названиями дней (например, "понедельник", "вторник", ..., "воскресенье").
Во многих языках (например, С и Pascal) переменные и их тип должны быть объявлены прежде, чем они будут использованы в программе. Если при этом переменная изначально была объявлена, например, как числовая, то уже нигде в программе ей нельзя присвоить значение какого-нибудь другого типа. Говорят, что это языки с сильным контролем типов. Однако есть языки со слабым контролем типов (их еще называют языками со свободными или динамическими типами), типичные примеры — JavaScript и РНР. В них тип переменной не фиксируется раз и навсегда в пределах программы, одна и та же переменная в различных фрагментах программы может принимать значения то одного, то другого типа по усмотрению программиста.
Пока мы не будем углубляться в детали реализации идеи переменных в конкретных языках. Также мы не затронули еще одну важную тему, связанную с переменными, а именно области их видимости. Дело в том, что переменные могут быть глобальными и локальными. Глобальные доступны (т. е. видны) из любого места программы, локальные — только в том блоке программного кода (фрагменте программы), где они были созданы. Типичный пример блока кода — функция. Понятие функции мы рассмотрим позже, а пока лишь отметим, что она может содержать в своем коде переменные, недоступные (невидимые) из внешней по отношению к ней части программы.