Первые программы - Язык Паскаль и начала программирования

Программирование: введение в профессию. 1: Азы программирования - 2016 год

Первые программы - Язык Паскаль и начала программирования

В этой части книги мы попробуем перейти от слов к делу и научиться азам написания компьютерных программ, для чего нам потребуется язык Паскаль. Изначально Паскаль был предложен швейцарским учёным Никлаусом Виртом в 1970 году в качестве языка для обучения программированию; им же был написан самый первый компилятор Паскаля.

Довольно неожиданным для начинающих оказывается утверждение, что в наше время невозможно однозначно определить, что же такое, собственно, язык Паскаль. Дело в том, что за десятилетия истории Паскаля самые разные люди и организации создавали свои собственные трансляторы этого языка и при этом вносили в язык разнообразные расширения и изменения; такие видоизменённые варианты языка программирования обычно называют диалектами. К нынешнему времени понятие “язык Паскаль” настолько размыто, что всегда требует уточнений. Как исходное определение языка, данное Виртом, так и существующие стандарты Паскаля очень ограничены в своих возможностях, и на них, собственно говоря, давно никто не оглядывается.

Реализация, которую мы будем использовать в качестве учебного пособия, называется Free Pascal и отсчитывает свою историю с 1993 года; её автор Флориан Пол Клэмпфль (Florian PaulKlampfl) начал разработку собственного компилятора Паскаля в ответ на заявление фирмы Борланд о прекращении развития линейки компиляторов Паскаля для MS-DOS. В настоящее время компилятор Free Pascal доступен для всех наиболее популярных операционных систем, включая Linux и FreeBSD (а также Windows, MacOSX, OS/2, iOS; на момент написания этой книги было заявлено о скором появлении поддержки для Android). Free Pascal поддерживает сразу несколько различных диалектов Паскаля (на выбор программиста) и включает огромное количество разнообразных возможностей, пришедших из других версий Паскаля.

Как ни странно, изучать всё это разнообразие мы не станем; напротив, набор возможностей, которыми мы станем пользоваться на протяжении этой части книги, будет весьма ограниченным. Дело в том, что Free Pascal интересует нас не сам по себе, не в качестве инструмента для профессионального программирования (хотя он, вне всякого сомнения, может выступать в качестве такого инструмента), а лишь как учебное пособие, которое позволит нам освоить начала и основные приёмы императивного программирования. В дальнейшем нас ожидает знакомство с языками Си и Си++, и подходить к их изучению желательно с уже сформированными представлениями о структурном программировании, рекурсии, указателях и других базовых возможностях, которые используются в компьютерных программах; Паскаль как раз и позволит нам всё это изучить, но весь Паскаль (и тем более возможности Free Pascal во всём их многообразии) для этого нам не потребуются.

При желании читатель, несомненно, сможет самостоятельно довести своё знание как Free Pascal, так и других реализаций Паскаля до любого уровня, какого пожелает, воспользовавшись литературой и другими материалами, в изобилии представленными в Интернете; однако вполне возможно, что после знакомства с другими языками программирования использовать в качестве профессионального инструмента именно Паскаль читателю уже не захочется. Так или иначе, вопрос о выборе профессионального инструмента — пока что дело будущего.

Задачу, стоящую перед нами сейчас, можно сформулировать одной фразой: мы на самом деле изучаем не Паскаль, а программирование как таковое, Паскаль же интересует нас лишь как иллюстрация, не более того.

2.1. Первые программы

Напомним для начала кое-что из того, что уже обсуждалось во вводной части. Компьютер — точнее, его центральный процессор — может выполнять программу, представленную в машинном коде, но для человека писать машинный код практически нереально из-за грандиозной трудоёмкости такой работы. Поэтому программисты пишут программы в виде текста, соответствующего правилам того или иного языка программирования, а затем применяют программу-транслятор; это может быть либо компилятор, который переводит программу целиком в какое-то другое представление (например, в машинный код), либо интерпретатор, который ничего никуда не переводит, а просто выполняет шаг за шагом действия, предписанные текстом выполняемой программы. Текст, написанный программистом в соответствии с правилами языка программирования, называют исходным текстом или исходным кодом программы.

Язык Паскаль, который мы начинаем изучать, обычно относят к компилируемым; это означает, что в большинстве случаев при работе с Паскалем применяются именно компиляторы1. Сама программа на Паскале, как и едва ли не на любом языке программирования2, представляет собой текст в ASCII-представлении, которое мы обсуждали в § 1.6.4. Следовательно, для написания программы нам придётся воспользоваться каким-нибудь из текстовых редакторов; о некоторых из них мы рассказали в §1.4.10. Результат мы сохраним в файле с именем, имеющем суффикс3“.pas”, который обычно означает текст программы на Паскале. Затем мы запустим компилятор, который в нашем случае называется fpc от слов Free Pascal Compiler, и если всё будет в порядке, то результатом наших упражнений станет исполняемый файл, который мы сможем запустить.

Для наглядности и удобства мы перед началом наших экспериментов создадим пустую директорию4 и все эксперименты будем проводить в ней. В примерах диалогов с компьютером мы здесь и далее воспроизведём приглашение командной строки, состоящее из имени пользователя (avst), имени машины (host), текущей директории (напомним, что домашняя директория пользователя обозначается символом “˜”) и знака “4”, традиционно используемого в приглашении. Итак, начнём:

Как видим, мы создали директорию firstprog, зашли в неё (то есть сделали её текущей) и, дав команду ls, убедились, что она пока что пустая, то есть не содержит ни одного файла. Если что-то здесь оказалось не совсем понятно, обязательно (и немедленно!) перечитайте § 1.4.4.

Теперь самое время запустить редактор текстов и набрать в нём текст программы. Автор этих строк предпочитает редактор vim, но если вы совсем не хотите его изучать (а это, надо признать, всё-таки не очень просто), с тем же успехом вы можете воспользоваться другими редакторами, такими как joe или nano. Во всех случаях принцип запуска редактора текстов одинаков. Сам по себе редактор — это просто программа, имеющая имя для запуска, и этой программе следует сразу при запуске указать (в виде параметра) имя файла, который вы хотите редактировать. Если такого файла ещё нет, редактор создаст его при первом сохранении.

Программа, которую мы напишем, будет очень простой: вся её работа будет заключаться в том, чтобы выдать на экран5 строку на английском языке, каждый раз одну и ту же. Практической пользы от такой программы, конечно, никакой нет, но нам сейчас важнее заставить работать хоть какую-нибудь программу, просто чтобы убедиться, что мы это можем. В примере программа будет выдавать фразу Hello, world!6, так что мы назовём файл её исходного текста hello.pas. Итак, запускаем редактор (если хотите, подставьте вместо vim слово joe или nano):

Теперь нам нужно набрать текст программы. Выглядеть он будет вот так:

Сейчас самое время дать некоторые пояснения. Первая строчка программы — это так называемый заголовок, показывающий, что весь текст, идущий дальше, представляет собой программу (слово program), которую её автор назвал именем hello (вообще-то здесь можно написать любое имя, состоящее из латинских букв, цифр и символа подчёркивания, только начинаться это имя должно обязательно с буквы; такие имена мы будем называть идентификаторами). Заголовок заканчивается символом “точка с запятой”. Вообще говоря, современные реализации Паскаля позволяют не писать заголовок, но мы этим пользоваться не будем: программа без заголовка выглядит не так наглядно.

Слово begin, которое мы написали на второй строке, означает по-английски начало; в данном случае оно обозначает начало главной части программы, но наша программа настолько проста, что фактически из одной только этой “главной части” и состоит; позже мы будем писать программы, в которых “главная часть” будет совсем маленькой в сравнении со всем остальным.

Расположенное на следующей строке writeln(’Hello, world!’) как раз и делает то, для чего написана наша программа — выдаёт надпись “Hello, world!”. Поясним, что слово “write” по-английски означает “писать”, а загадочная добавка “ln” происходит от слова line и означает, что после выполнения печати нужно перевести строку (чуть позже мы этот момент рассмотрим подробнее). Получившееся слово “writeln” в Паскале используется для обозначения оператора вывода с переводом строки, а в скобках перечисляется всё, что оператор должен вывести; в данном случае это одна строка.

Читатель, уже знакомый с Паскалем, может возразить, что в большинстве источников writeln называют не оператором, а “встроенной процедурой”; но это не совсем корректно, ведь словом “процедура” (без эпитета “встроенная”) обозначается подпрограмма, которую пишет сам программист, а встроенными процедурами следует называть такие процедуры, которые программист мог бы написать, но ему не нужно это делать, поскольку компилятор их в себе уже содержит. Ничего похожего на writeln с его переменным числом параметров к директивами форматирования вывода мы сами написать не можем; иначе говоря, если бы этой “встроенной процедуры” в языке не было, мы не смогли бы сделать её сами. С writeln и другими подобными сущностями связан свой собственный синтаксис (двоеточие после аргумента, за которым идёт целочисленная ширина в знакоместах), то есть это именно часть языка Паскаль, которая обрабатывается компилятором по-своему, а не в соответствии с обобщёнными правилами. В такой ситуации называть writeln именно оператором, а не чем-то другим, кажется существенно логичнее. Справедливости ради отметим, что слова write, writeln, read, readln и т. п. компилятор рассматривает как обычные идентификаторы, а не как зарезервированные слова, что является весомым аргументом против отнесения этих сущностей к операторам; впрочем, Free Pascal относит эти слова к категории модификаторов (modifiers), в которую также входят, например, break и continue, которые никто не называет иначе как операторами.

Строка, предназначенная к печати, заключена в апострофы, чтобы показать, что этот фрагмент текста обозначает сам себя, а не какие-то конструкции языка. Если бы мы не поставили апострофов, компилятор попытался бы понять, что мы имеем в виду под словом “Hello”, и, не найдя никакого подходящего смысла, выдал бы сообщение об ошибке, а переводить нашу программу в машинный код бы в итоге не стал; но коль скоро слово заключено в апострофы, оно обозначает само себя и ничего больше, так что компилятору тут ни о чём задумываться не нужно. Последовательность символов, заключённая в апострофы и задающая текстовую строку, называется строковым литералом или строковой константой.

Слово “оператор” представляет собой пример довольно неудачного перевода английского термина; в оригинале эта сущность называется statement, что правильнее было бы перевести как “утверждение”, “предложение” или как-нибудь ещё.

Дело в том, что в математике традиционно оператором называется определённый класс отображений (то есть попросту функций), причём в этом значении слово “оператор” представляет собой точный перевод английского operator; совершенно естественно, что это английское слово проникло к в программистскую терминологию: при описании многих языков программирования словом operator англоязычные специалисты стали обозначать то, что мы по-русски называем операциями — сложение, вычитание, умножение, деление, сравнения и прочее в таком духе. Всё это не было проблемой до тех пор, пока не потребовалось перевести с английского текст, содержащий одновременно слова operator и statement. Даже с этим можно справиться, переводя operator как операцию, а не как оператор; но когда в очередном языке программирования (например, в C++) слово operator становится ключевым, проблемы с терминологией оказываются попросту неизбежны: когда человек видит слово “operator”, его довольно трудно убедить не произносить русского слова “оператор”.

Так или иначе, в русскоязычной программистской литературе оператором называют конструкцию языка программирования, предписывающую выполнение некоторого действия — не вычисления, а именно действия. Полезно помнить, что по-английски это называется вовсе не operator, a statement.

Последняя строка нашей программы состоит из слова end и точки. Слово end по-английски означает “конец” (опять же, в данном случае — конец главной части программы). Правила языка Паскаль требуют программу завершить точкой — то ли для верности, то ли просто для красоты.

Итак, текст набран, сохраняем его и выходим из редактора текстов (в vim’е для этого нажимаем Еsс-двоеточие-wq-Еntеr; в nano — Ctrl-O, Enter, Ctrl-X, в joe — Ctrl-K, потом ещё x; в дальнейшем мы не будем рассказывать, как то или иное действие сделать в разных редакторах, ведь в § 1.4.10 всё это уже обсуждалось). Вновь получив приглашение командной строки, убеждаемся, что теперь наша директория уже не пуста — в ней есть файл, который мы только что набрали:

Для наглядности дадим команду, показывающую содержимое файла; с большими текстами так лучше не делать, но наша программа состоит всего из четырёх строк, так что мы можем себе позволить лишний эксперимент. Сама команда называется, как мы уже знаем, cat, а параметром ей служит имя файла:

Кстати, пока файл в нашей директории всего один, можно не набирать его имя на клавиатуре, а нажать клавишу Tab, и интерпретатор командной строки сам напишет это имя за нас.

Теперь, убедившись, что файл у нас действительно есть и в нём написано то, что мы ожидали, мы можем запустить компилятор. Напоминаем, он называется fpc; как водится, ему нужен параметр, и это, в который уж раз, наше имя файла с исходным текстом:

Компилятор напечатает несколько строк, немного различающихся в зависимости от конкретной его версии. Если среди них затесалась строка, похожая на

можете смело не обращать на неё внимания, как и на все остальные строки, если только в них не встречается слово Error (ошибка), Fatal (фатальная ошибка), warning (предупреждение) или note (замечание). Сообщение об ошибке (со словом Error или Fatal) означает, что текст, который вы подсунули компилятору, не соответствует правилам языка Паскаль, так что результатов компиляции вы не получите — компилятор просто не знает, что делать. Предупреждения (со словом warning) выдаются, если текст программы формально соответствует требованиям языка, но из каких-то соображений компилятор считает, что результат будет работать не так, как вы ожидали (скорее всего, неправильно); единственным исключением является вышеприведённое предупреждение про output sections, его на самом деле выдаёт не компилятор, а вызванная им программа ld (компоновщик), и нас это предупреждение не касается. Наконец, замечания (сообщения со словом note) компилятор выдаёт, если какая-то часть программы кажется ему странной, хотя и не должна, по его мнению, приводить к неправильной работе.

Например, если бы мы вместо writeln написали writenl, а потом ещё забыли бы поставить точку в конце программы, то увидели бы, помимо прочего, примерно такие сообщения:

Первым сообщением компилятор ставит нас в известность, что слова writenl он не знает, так что из нашей программы ничего хорошего уже не получится; второе сообщение означает, что файл кончился, а компилятор так к не дождался точки, к это его настолько огорчило, что рассматривать нашу программу дальше он вообще отказывается (этим Fatal отличается от просто Error).

Обратите внимание на цифры в скобках после имени файла; hello.pas(3,12) означает, что ошибка обнаружена в файле hello.pas, в строке №3, в столбце № 12, a hello.pas(5) означает ошибку в строке № 5 — такой в нашей программе вообще-то нет, но к тому времени, когда компилятор обнаружил, что файл неожиданно кончился, строка №4 уже тоже осталась позади, ну а что в пятой строке ничего нет — это уже другой вопрос.

Номера строк, выдаваемые вместе с сообщениями об ошибках и предупреждениями — это очень ценная информация, которая позволяет нам быстро найти то место в нашей программе, где компилятору что-то не понравилось. Вне зависимости от того, каким редактором текстов вы пользуетесь, желательно сразу же разобраться, как в нём найти строку по номеру, иначе программировать будет очень сложно.

Убедиться, что компиляция прошла успешно, можно, в очередной раз посмотрев содержимое текущей директории с помощью команды ls:

Как видим, файлов стало больше. Файл hello.o нас не очень интересует, это так называемый объектный модуль, который компилятор подаёт на вход компоновщику, а потом почему-то забывает удалить; а вот файл, который называется просто hello без суффикса — это и есть то, ради чего мы запускали компилятор: исполняемый файл, то есть попросту файл, содержащий машинный код, соответствующий нашей программе. Для верности попробуем получить об этих файлах больше информации:

В первой колонке видим у файла hello установленные права на исполнение (буква “х”). Заодно замечаем, насколько этот файл больше, чем исходный текст: файл hello.pas занимает всего 47 байт (по количеству символов в нём), тогда как получившийся исполняемый файл “весит” более 120 килобайт. На самом деле всё не так уж плохо: как мы вскоре сможем убедиться, с ростом исходной программы получающийся исполняемый файл почти не будет увеличиваться. Просто компилятор вынужден загнать в исполняемый файл сразу всё, что нужно для выполнения практически любых операций ввода-вывода, а мы эти возможности пока почти не используем.

Осталось лишь запустить полученный файл. Делается это так:

Поясним, почему обязательно надо написать “./hello”, а не просто “hello”. Если в имени команды нет ни одного символа “/”, командный интерпретатор пытается найти встроенную команду с таким именем, а если такой нет — то выполняет поиск исполняемого файла, причём ищет он этот файл в системных директориях, таких как /bin, /usr/bin, /usr/local/bin к т. п.; точнее говоря, поиск выполняется в директориях, которые перечислены в переменной окружения PATH; вы можете увидеть список этих директорий примерно следующим образом:

Ясно, что вашей текущей (рабочей) директории в этом списке нет; если написать просто “hello”, команды с таким именем командный интерпретатор не найдёт и выдаст сообщение об ошибке. Если же в имени команды присутствует символ “/”, который, как мы знаем, используется для отделения имени файла от имени директории, а также имён директорий друг от друга, то командный интерпретатор считает, что ему уже задано, какой именно файл запускать, и искать ничего не надо. Осталось сказать, что точкой обозначается текущая директория, какова бы она ни была; следовательно, команда “./hello” означает дословно “запустить файл hello из текущей директории”, что нам и требуется.

Естественно, в программе может быть больше одного оператора; в простейшем случае они будут исполняться один за другим. Рассмотрим для примера такую программу:

Теперь у нас в программе не один оператор, как раньше, а два; можно заметить, что между операторами ставится символ “точка с запятой”. Обычно точку с запятой ставят в конце очередного оператора, чтобы отделить его от следующего, если, конечно, этот “следующий” есть; если его нет, точка с запятой не нужна. Отметим, что слово end оператором не является, так что точку с запятой перед ним обычно не ставят.

Если эту программу откомпилировать и запустить, она напечатает сначала (в результате выполнения первого оператора) “Hello, world!”, а затем (в результате выполнения второго оператора) “Good bye, world.”:

Вернёмся теперь немного назад и поясним чуть более подробно буквы “ln” в названии оператора writeln, которые, как мы уже сказали, означают перевод строки. В языке Паскаль присутствует также оператор write, работающий абсолютно так же, как и writeln, но не делающий перевода строки по окончании операции вывода. Попробуем отредактировать нашу первую программу, убрав буквы “ln”:

После этого снова запустим компилятор fpc, чтобы обновить наш исполняемый файл, и посмотрим, как наша программа будет работать теперь. Картина на экране получится примерно такая:

В этот раз, как и раньше, наша программа выдала надпись “Hello, world!”, но строку не перевела; когда после её завершения интерпретатор командной строки напечатал очередное приглашение, оно появилось в той же строке, где и выдача нашей программы.

Приведём ещё один пример. Напишем такую программу:

Сохраним эту программу в файл, например, nldemo.pas, откомпилируем и запустим:

В программе мы вывели первое и третье слово с помощью write, тогда как второе и четвёртое — с помощью writeln, то есть с переводом строки. Эффект от этого наблюдается вполне наглядный: слово “Second” выведено в той же строке, что и “First” (после которого программа не сделала перевода строки), так что они слились вместе; то же самое произошло со словами “Third” и “Fourth”.

Прежде чем завершить разговор о простейших программах, отметим ещё один очень важный момент — то, как мы расположили друг относительно друга разные части наших исходных текстов. Заголовок программы, а также слова begin и end, обозначающие начало и конец её главной части, мы писали в начале строки, тогда как операторы, составляющие главную часть, мы сдвинули вправо, поставив перед ними четыре пробела; кроме того, все эти элементы мы расположили каждый на отдельной строке.

Интересно, что компилятору всё это совершенно не нужно. Мы могли бы написать, например, и так:

image14

или вот так:

image14

Что касается нашей второй программы, в которой больше операторов, то там гораздо больше и возможностей для бардака — например, можно было бы написать что-то вроде

image14

С точки зрения компилятора при этом ничего бы не изменилось; более того, пока речь идёт о совсем примитивных программах, вряд ли что-то изменится и с нашей собственной точки зрения, разве что взбунтуется наше эстетическое чувство. Однако для мало-мальски сложных программ ситуация резко меняется. Читатель вскоре сам сможет увидеть, что в текстах программ достаточно тяжело разбираться; иначе говоря, чтобы по имеющемуся тексту программы понять, что она делает и как она это делает, нужно прилагать интеллектуальные усилия, которые во многих случаях превышают усилия, требующиеся для написания текста программы с нуля. Если при этом текст ещё и написан как попало, разобраться в нём оказывается, как говорил известный герой Булгакова, решительно невозможно. Самое интересное, что безнадёжно заблудиться можно не только в чужой программе, но и в своей собственной, причём иногда это происходит раньше, чем программа будет дописана. Нет ничего проще (и обиднее), чем запутаться в собственном коде, не успев толком ничего написать.

На практике программистам приходится читать программы — как свои, так и чужие — едва ли не больше времени, чем писать их, так что, вполне естественно, на читаемость программного кода приходится обращать самое пристальное внимание. Опытные программисты говорят, что текст программы предназначен прежде всего для прочтения человеком, и лишь во вторую очередь — для обработки компьютером. Практически все существующие языки программирования предоставляют программисту определённую свободу по части оформления кода, что позволяет самую сложную программу сделать понятной с первого взгляда любому её читателю, знакомому с используемым языком; но с тем же успехом можно сделать очень простую программу непонятной её собственному автору.

Для улучшения читаемости программ применяется целый ряд приёмов, которые вместе составляют грамотный стиль оформления программного кода, и одним из самых важных моментов здесь является применение структурных отступов. Почти все современные языки программирования допускают в начале любой строки текста произвольное количество7 пробельных символов — пробелов или табуляций, что позволяет оформить программу так, чтобы её структуру можно было “схватить” расфокусированным взглядом, не вчитываясь. Как мы вскоре увидим, структура программы формируется по принципу вложения одного в другое; техника структурных отступов позволяет подчеркнуть структуру программы, попросту сдвигая вправо любые вложенные конструкции относительно того, во что они вложены.

В наших простейших программах уровень вложения оказался только один: операторы writeln и write оказались вложены в главную часть программы. При этом ни заголовок, ни сама главная часть ни во что не вложены, так что их мы написали, начиная с крайней левой колонки символов в тексте программы, а операторы writeln и write сдвинули вправо, чтобы показать, что они вложены в главную часть программы. Единственное, что мы здесь выбрали достаточно произвольно — это размер структурного отступа, который в наших примерах на протяжении всей книги составит четыре пробела. В действительности во многих проектах (в частности, в ядре ОС Linux) для структурного отступа используют символ табуляции, причём ровно один. Можно встретить размер отступа в два пробела — именно такие отступы приняты в коде программ, выпускаемых Фондом свободного программного обеспечения (FSF). Совсем редко используется три пробела; такой размер отступа иногда встречается в программах, написанных для Windows. Другие размеры отступа использовать не следует, и этому есть ряд причин. Одного пробела слишком мало для визуального выделения блоков, левый край текста при этом воспринимается как нечто плавное и не служит своей цели. Количество пробелов, превышающее четыре, трудно вводить: если их больше пяти, их приходится считать при вводе, что сильно замедляет работу, но и пять пробелов оказывается вводить очень неудобно. Если же использовать больше одной табуляции, то на экран по горизонтали почти ничего не поместится.

Выбрав какой-то один размер отступа, его следует придерживаться во всём тексте программы. Это касается также и других решений, принятых по поводу стиля оформления: исходный текст программы должен быть стилистически однородным, иначе его читателям придётся постоянно перестраивать своё восприятие, что довольно утомительно.






Для любых предложений по сайту: [email protected]