Лекция 1 – Языки программирования. Visual Basic

{Материал из Википедии - свободной энциклопедии}Язы́к программи́рования - формальнаязнаковая система, предназначенная для описанияалгоритмов в форме, которая удобна для исполнителя (например,компьютера ). Язык программирования определяет наборлексических ,синтаксических исемантических правил, используемых при составлениикомпьютерной программы . Он позволяетпрограммисту точно определить то, на какие события будет реагировать компьютер, как будут храниться и передаватьсяданные , а также какие именно действия следует выполнять над этими данными при различных обстоятельствах. Со времени создания первых программируемых машин человечество придумало уже более двух с половиной тысяч языков программирования (См.Список языков программирования (англ.)). Каждый год их число пополняется новыми. Некоторыми языками умеет пользоваться только небольшое число их собственных разработчиков, другие становятся известны миллионам людей. Профессиональные программисты иногда применяют в своей работе более десятка разнообразных языков программирования.Cоздатели языков по-разному толкуют понятиеязык программирования . Среди общих мест, признаваемых большинством разработчиков, находятся следующие:

    Функция: язык программирования предназначен для написания компьютерных программ, которые применяются для передачи компьютеруинструкций по выполнению того или иноговычислительного процесса и организации управления отдельнымиустройствами .

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

    Исполнение: язык программирования может использовать специальные конструкции для определения и манипулированияструктурами данных и управления процессом вычислений.

Особенности языков программирования

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

    Типов и структур данных

    Операционную семантику (алгоритм вычисления конструкций языка)

    Семантические конструкции языка

    Библиотеки примитивов (например, инструкции ввода-вывода)

    Философии назначения и возможностей языка

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

Типы данных

Во внутреннем представлении все данные в современных цифровых компьютерах сохраняются просто как нули и единицы (в двоичном виде ). Данные, которые представляют информацию из реального мира (имена, банковские счета, измерения и др.) - это низкоуровневые двоичные данные, которые преобразуются языками программирования в эти высокоуровневые концепции.

Особая система, по которой данные организуются в программе, - это система типов языка программирования; разработка и изучение систем типов известна под названиемтеория типов . Языки могут быть классифицированы как системысо статической типизацией и языки сдинамической типизацией . Статически-типизированные языки могут быть в дальнейшем подразделены на языки собязательной декларацией , где каждая переменная и объявление функции имеет обязательное объявление типа, и языки свыводимыми типами . Иногда динамически-типизированные языки называютсялатентно типизированными .

Структуры данных

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

Компилируемые и интерпретируемые языки

Языки программирования делятся на два класса - компилируемые иинтерпретируемые . Программа на компилируемом языке при помощи специальной программыкомпилятора преобразуется (компилируется ) в набор инструкций для данного типа процессора (машинный код) и далее записывается висполняемый файл , который может быть запущен на выполнение как отдельная программа. Другими словами, компилятор переводит программу с языка высокого уровня на низкоуровневый язык, понятный процессору. Если программа написана на интерпретируемом языке, тоинтерпретатор непосредственно выполняет (интерпретирует ) ее текст без предварительного перевода. При этом программа остается на исходном языке и не может быть запущена без интерпретатора. Можно сказать, что процессор компьютера - это интерпретатор машинного кода. Кратко говоря, компилятор переводит программу на машинный язык сразу и целиком, создавая при этом отдельную программу, а интерпретатор переводит на машинный язык прямо во время исполнения программы. Разделение на компилируемые и интерпретируемые языки является несколько условным. Так, для любого традиционно компилируемого языка, как, например,Паскаль , можно написать интерпретатор. Кроме того, большинство современных «чистых» интерпретаторов не исполняют конструкции языка непосредственно, а компилируют их в некоторое высокоуровневое промежуточное представление (например, с разыменованием переменных и раскрытием макросов). Для любого интерпретируемого языка можно создать компилятор - например, язык Лисп, изначально интерпретируемый, может компилироваться без каких бы то ни было ограничений. Создаваемый во время исполнения программы код может так же динамически компилироваться во время исполнения. Как правило, скомпилированные программы выполняются быстрее и не требуют для выполнения дополнительных программ, так как уже переведены на машинный язык. Вместе с тем при каждом изменении текста программы требуется ее перекомпиляция, что создает трудности при разработке. Кроме того, скомпилированная программа может выполняться только на том же типе компьютеров и, как правило, под той же операционной системой, на которую был рассчитан компилятор. Чтобы создать исполняемый файл для машины другого типа, требуется новая компиляция. Интерпретируемые языки обладают некоторыми специфическими дополнительными возможностями (см. выше), кроме того, программы на них можно запускать сразу же после изменения, что облегчает разработку. Программа на интерпретируемом языке может быть зачастую запущена на разных типах машин и операционных систем без дополнительных усилий. Однако интерпретируемые программы выполняются заметно медленнее, чем компилируемые, кроме того, они не могут выполняться без дополнительной программы-интерпретатора. Некоторые языки, например,Java иC# , находятся между компилируемыми и интерпретируемыми. А именно, программа компилируется не в машинный язык, а в машинно-независимый код низкого уровня,байт-код . Далее байт-код выполняетсявиртуальной машиной . Для выполнения байт-кода обычно используется интерпретация, хотя отдельные его части для ускорения работы программы могут быть транслированы в машинный код непосредственно во время выполнения программы по технологии компиляции «на лету» (Just-in-time compilation,JIT ). Для Java байт-код исполняется виртуальной машиной Java (Java Virtual Machine,JVM ), для C# -Common Language Runtime . Подобный подход в некотором смысле позволяет использовать плюсы как интерпретаторов, так и компиляторов. Следует упомянуть также оригинальный языкФорт(Forth) , который является как бы одновременно интерпретируемым и компилируемым.

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

Целью компиляции является реализация возможности выполнения реальным компьютером кода, написанного для абстрактной машины. Компиляция , однако, – лишь одна из двух базисных технологий, применяемых для достижения нашей цели.

Базисные схемы

Вместо компиляции программы можно ее интерпретировать. Рисунок ниже иллюстрирует разницу подходов, игнорируя роль входных данных.


Рис. 3.6.

И компилятор, и интерпретатор являются программами, на вход которых подаются программы, написанные на языке программирования. Компилятор транслирует переданную ему программу в целевую форму – машинный код, который уже может быть непосредственно выполнен на компьютере, создавая выходные результаты вычислений. Обработка той же исходной программы интерпретатором не создает целевой программы, но непосредственно выполняет ее, получая выходные результаты.

Интерпретатор должен быть способен определить эффект выполнения каждой конструкции языка программирования. Как пример того, как интерпретатор выполняет свою задачу, рассмотрим интерпретацию присваивания x: = x +1 . Интерпретатор должен хранить таблицу всех используемых переменных и связанных с ними значений. Он вычисляет новое значение x , добавляя 1 к старому значению, хранящемуся в таблице, а затем выполняет присваивание, заменяя старое значение x значением вычисленного выражения.

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

В дополнительном упражнении для небольшого языка потребуется, применяя эти идеи, написать компилятор и интерпретатор.

Если рассматривать язык программирования как машинный код для абстрактной машины, можно сказать, что интерпретатор – это программа, моделирующая вычисление на этой машине. Машинная память в этом случае также абстрактна и представляет структуры данных в виде таблиц интерпретатора "переменные – значения".

На следующем рисунке процессы компиляции и интерпретации дополнены вводом данных:


Рис. 3.7.

Рисунок демонстрирует еще одну разницу между компилятором и интерпретатором. У интерпретатора два источника ввода – исходная программа и входные данные; а компилятору подается только программа. В последующем обсуждении этому различию придадим математическую форму.

Компилировать или интерпретировать? Эта проблема – предмет широко рассмотрения в компьютерных науках. Что лучше: непосредственно обрабатывать исходную информацию в том виде, как она есть, или предварительно привести ее к более удобной форме? Этот вопрос стоит не только при обработке программ, мы будем сталкиваться с ним и при изучении алгоритмов.

У компиляторов и интерпретаторов имеются свои достоинства. Возможны различные критерии. По производительности – времени выполнения программы – компиляторы побеждают.

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

Все меняется, если в качестве критерия выбрать удобство и скорость разработки. Компилятор стоит между вами и реализацией вашей последней идеи: прежде чем увидеть результаты последнего изменения в программе, необходимо ждать результата компиляции (и связывания, о чем ниже пойдет речь). При интерпретации выполнение начинается незамедлительно.

В современных средах разработки этот недостаток компиляции не является столь критическим благодаря применяемой технологии "возрастающей компиляции" , когда при внесении изменений компилируются только те части программы, на которых это изменение сказывается. В конце этой лекции мы рассмотрим, как эта технология работает в EiffelStudio.

Еще один критерий, согласно которому предпочтение опять-таки отдается компиляции, состоит в надежности программы. Компиляторы не просто транслируют программу – в процессе компиляции они осуществляют различные проверки, например, контроль типов для статически типизированных языков . Тем самым многие ошибки устраняются еще на этапе компиляции, в то время как для режима интерпретации ошибки обнаруживаются в процессе выполнения на одном из сеансов работы.

В принципе, интерпретатор также способен выполнять некоторые из проверок перед выполнением программы. Фактически, чистые интерпретаторы не применяются, они представляют собой всегда некоторую смесь компиляции и интерпретации.

Комбинирование компиляции и интерпретации

Схемы чистой компиляции и чистой интерпретации являются предельными вариантами: большинство практических решений является смесью. Это верно и для процесса компиляции в EiffelStudio, который будет рассмотрен позже в этой лекции.

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

Комбинирование интерпретации и компиляции идет значительно дальше рассмотренной основной идеи. Выход компилятора не должен быть непосредственным машинным кодом, он может быть субъектом дальнейшего процесса обработки.


Рис. 3.8.

Смешанная стратегия предполагает, что компилятор создает код на промежуточном языке , понимаемом некоторой виртуальной машиной – VM на рисунке. Такой подход объединяет преимущества компиляции и интерпретации. Благодаря тщательно спроектированной виртуальной машине возможно получить:

  • переносимость, так как VM-код не зависит от специфики физических процессоров;
  • повышение эффективности, поскольку создаваемый промежуточный код легко интерпретируется.

Виртуальные машины, байт-код и JIT (Just In Time) компиляторы

Реализация современных языков – Java, C#, других языков.Net – основана на смешанном решении. Промежуточный код для Java называется байт-кодом. В термине отражается тот факт, что виртуальная машина использует компактные команды, подобные командам фактического процессора, где каждая команда содержит код команды – типично задаваемый одним байтом, – после которого следует 0, 1 или 2 аргумента команды.

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

Прием двухэтапной компиляции был использован еще в семидесятые годы при реализации компилятора с языка Паскаль. Он получил второе рождение с распространением Интернета, так как хорошо был приспособлен для локального выполнения Web-клиентами. Поставщики апплетов – небольших программ – могли компилировать их в байт-код и поставлять их в такой форме. Дополнительным преимуществом к компактности стала переносимость кода, поскольку в противном случае машинный код пришлось бы создавать для каждой возможной целевой платформы .

Для выполнения апплета пользователям необходим только интерпретатор байт-кода. Они даже не должны знать, что такой интерпретатор существует, если он встроен в их Web-браузер, Поскольку при таком подходе возникают потенциальные риски, связанные с безопасностью, – жульнические или некорректные апплеты могут повредить ваш компьютер, – по этой причине для апплетов необходим интепретатор, который будет строго контролировать операции, разрешенные для апплетов.

Поставка программ через апплеты достигла некоторого успеха, но не стала основным способом распределенного ПО, как ожидалось в момент появления Java. Частично это связано с проблемами безопасности, но главная причина – в потере эффективности, возникающей по причине интерпретации. Большинство успешных апплетов являются небольшими программами, предназначенными для выполнения на Web-странице, включающие визуальную компоненту, при наличии которой потери времени представляются несущественными.

Для улучшения эффективности времени выполнения байт-кода применяются JIT (Just In Time) компиляторы, называемые джитерами, – осуществляющие компиляцию по требованию. Основная идея состоит в том, что машинный код для некоторого модуля создается "на лету", в тот момент, когда он первый раз вызывается на выполнение (не следует путать любителя джаза –jitterbug, с ошибками такого компилятора – jitter bug ). Внесем соответствующие дополнения в предыдущий рисунок, который теперь выглядит так:


Рис. 3.9.

Обычно, как показано на рисунке, наряду с компиляцией "на лету" (джитингом) остается и возможность интерпретации байт-кода. Компиляция "на лету" обычно имеет место при первом использовании модуля (метода или всего класса), так что она будет нужна только для кода, фактически используемого в этом сеансе выполнения. В сравнении с традиционным компилятором, который компилирует всю программу, такой подход позволяет создавать более компактный код, сокращает время компиляции, но, что более важно, делает компиляцию частью процесса выполнения. Последнее является серьезным недостатком, поскольку к времени выполнения добавляются расходы на компиляцию, так что само время выполнения становится менее предсказуемым.

С первого взгляда кажется, что при таком подходе не стоит выполнять проверки типов и другой контроль, поскольку кому же хочется во время выполнения получать сообщения о нарушении согласованности типов ? Это возвращало бы нас к проблемам динамически типизированных языков . Конечно, нам хотелось бы, чтобы все необходимые проверки выполнялись на первом шаге компиляции при создании байт-кода, так, чтобы любой код, передаваемый джитеру, был безопасным. К сожалению, эти утешительные предположения нереалистичны в распределенной среде, где опять возникают проблемы безопасности. Если вы загружаете байт-код из сайта, то можете ли вы знать, прошел ли он проверку? В общем случае – нет. Но тогда нарушения типа могут стать не только причиной нарушения надежности и аварийного завершения программы, все может быть гораздо хуже: в результате атаки становится возможным нарушение безопасности.

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

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

Стратегия компиляции в EiffelStudio также включает байт-код, но, как мы увидим, она использует различные способы комбинирования интерпретации и компиляции.

Cтраница 1


Интерпретируемые языки часто используют в качестве учебных. Для освоения компилирующих языков нужны недели и месяцы подготовки, а писать простые программы на интерпретируемом языке можно уже на первый-второй день после начала занятий. Простейшим интерпретируемым языком программирования считается язык BASIC, с основами которого мы познакомимся в нашей книге.  

Интерпретируемый язык (interpretive language) - язык программирования, программы на котором компьютер считывает, транслирует и исполняет немедленно, строка за строкой; примером служит интерпретируемый Бейсик.  

Геделева нумерующая позволяет рассматривать интерпретируемые языки не только как языки, описывающие натуральные числа (т.е. имеющие множество натуральных чисел в качестве области их предполагаемой интерпретации), но и как относящиеся к нумерованным выражениям. При этом возникает возможность того, что некоторые предложения, очевидным образом относящиеся к некоторым числам, имея в виду геделеву нумерующую, можно считать относящимися к некоторым выражениям, идентичным самим этим предложениям. Описываемое положение дел не просто возможно: доказательство леммы о диагонализации выявляет, как подобная ситуация возникает, а последующие теоремы показывают, как ее можно использовать.  

Лисп является в первую очередь интерпретируемым языком. Пользователь может просто попробовать новые идеи н получить непосредственный отклик об их плодотворности. Программы не нужно транслировать, и их можно исправлять в процессе исполнения. Оглаженную функцию можно передать на трансляцию, тогда она выполняется быстрее. В одной и той же программе могут быть транслированные и интерпретируемые функции. Оттранслированную один раз функцию не нужно транслировать вновь из-за ошибок в других функциях. Транслирование по частям (incremental compiling) экономит усилия программиста и время вычислительной машины.  

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

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

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

Язык представления, подобный тому, что используется в STRIPS, с точки зрения программной реализации является интерпретируемым языком, т.е. трансляция с этого языка выполняется интерпретатором, программой, которая способна распознавать в операторах языка формулы, подобные ризЬ (ящик1, комнатаБ, комнатаА), и выразить заложенный в формулах смысл в терминах выполняемых процедур.  

Еше одно преимущество компилируемых языков программирования, таких как C, состоит в том, что полученные программы могут выполняться на компьютерах без компилятора. При работе же с интерпретируемыми языками для выполнения готовой программы нужно обязательно иметь соответствующую программу-интерпретатор.  

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

Итак, без циклов все-таки обойтись не удается, но при необходимости их использования следует обратить внимание еще на один аспект - выделение памяти для хранения переменных. Язык программирования MATLAB, являясь интерпретируемым языком сверхвысокого уровня, скрывает от пользователя операции, связанные с выделением и освобождением памяти. В результате мы чаще всего даже не задумываемся над тем, что в действительности происходит при выполнении простейшего (с точки зрения пользователя) оператора присваивания.  

Как известно, существуют две обширные категории программ - интерпретаторы и компиляторы, которые транслируют (переводят) программу, написанную на языке высокого уровня, в машинный код. Программа, написанная на интерпретируемом языке, записывается в память в виде последовательности команд языка высокого уровня.  

Однако бывают случаи, когда от программы не требуется быстродействие и она не предназначена для распространения, а сделана для себя. В этом случае удобно применить интерпретируемый язык программирования. Интерпретатор все равно переводит каждую строку по отдельности.  

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