М. Бен-Ари - Языки программирования. Практический сравнительный анализ (2000) (1160781), страница 6
Текст из файла (страница 6)
Обратите внимание, что для переменной должен быть определен конкретный тип по той простой причине, что компилятор должен знать, сколько памяти для нее нужно выделить! Константа — это просто переменная, которая не может изменяться. Пока мы не дошли до обсуждения объектно-ориентированного программирования, мы будем использовать знакомый термин «переменная» в более общем смысле, для обозначения и константы, и переменной вместо точного термина «объект».
2.4. Оператор присваивания
Удивительно, что в обычных языках программирования есть только один оператор, который фактически что-то делает, — оператор присваивания. Все другие операторы, такие как условные операторы и вызовы процедур, существуют только для того, чтобы управлять последовательностью выполнения операторов присваивания. К сожалению, трудно формально определить смысл оператора присваивания (в отличие от описания того, что происходит при его выполнении); фактически, вы никогда не встречали ничего подобного при изучении математики в средней школе и колледже. То, что вы изучали, — были уравнения:
ах2 + bх + с = О
Вы преобразовывали уравнения, вы решали их и выполняли соответствующие вычисления. Но вы никогда их не изменяли: если х представляло число в одной части уравнения, то оно представляло то же самое число и в другой части уравнения.
Скромный оператор присваивания на самом деле является очень сложным и решает три разные задачи:
1. Вычисление значения выражения в правой части оператора.
2. Вычисление выражения в левой части оператора; выражение должно определять адрес ячейки памяти.
3. Копирование значения, вычисленного на шаге 1, в ячейки памяти, начиная с адреса, полученного на шаге 2.
Таким образом, оператор присваивания
a(i + 1) = b + c;
несмотря на внешнее сходство с уравнением, определяет сложное вычисление.
2.5. Контроль соответствия типов
В трехшаговом описании присваивания в результате вычисления выражения получается значение конкретного типа, в то время как вычисление левой части дает только начальный адрес блока памяти. Нет никакой гарантии, что адрес соответствует переменной того же самого типа, что и выражение; фактически, нет даже гарантии, что размеры копируемого значения и переменной совпадают.
Контроль соответствия типов — это проверка того, что тип выражения совместим с типом адресуемой переменной при присваивании. Сюда входит и присваивание фактического параметра формальному при вызове процедуры.
Возможны следующие подходы к контролю соответствия типов:
• Не делать ничего; именно программист отвечает за то, чтобы присваивание имело смысл.
• Неявно преобразовать значение выражения к типу, который требуется в левой части.
• Строгий контроль соответствия типов: отказ от выполнения присваивания, если типы различаются.
Существует очевидный компромисс между гибкостью и надежностью: чем строже контроль соответствия типов, тем надежнее будет программа, но потребуется больше усилий при программировании для определения подходящего набора типов. Кроме того, должна быть обеспечена возможность при необходимости обойти такой контроль. Наоборот, при слабом контроле соответствия типов проще писать программу, но зато труднее находить ошибки и гарантировать надежность программы. Недостаток контроля соответствия типов состоит в том, что его реализация может потребовать дополнительных затрат во время выполнения программы. Неявное преобразование типов может оказаться хуже полного отсутствия контроля, поскольку при этом возникает ложная уверенность, что все в порядке.
Строгий контроль соответствия типов может исключить скрытые ошибки, которые обычно вызываются опечатками или недоразумениями. Это особенно важно в больших программных проектах, разрабатываемых группами программистов; из-за трудностей общения, смены персонала, и т.п. очень сложно объединять такое программное обеспечение без постоянной проверки, которой является строгий контроль соответствия типов. Фактически, строгий контроль соответствия типов пытается превратить ошибки, возникающие во время выполнения программы, в ошибки, выявляемые при компиляции. Ошибки, проявляющиеся только во время выполнения, часто чрезвычайно трудно найти, они опасны для пользователей и дорого обходятся разработчику программного обеспечения в смысле отсрочки сдачи программы и испорченной репутации. Цена ошибки компиляции незначительна: вам, вероятно, даже не требуется сообщать своему начальнику, что во время компиляции произошла ошибка.
2.6. Управляющие операторы
Операторы присваивания обычно выполняются в той последовательности, в какой они записаны. Управляющие операторы используются для изменения порядка выполнения. Программы на языке ассемблера допускают произвольные переходы по любым адресам. Язык программирования по аналогии может включать оператор goto, который осуществляет переход по метке на произвольный оператор. Программы, использующие произвольные переходы, трудно читать, а следовательно, изменять и поддерживать.
Структурное программирование — это название, данное стилю программирования, который допускает использование только тех управляющих операторов, которые обеспечивают хорошо структурированные программы, легкие для чтения и понимания. Есть два класса хорошо структурированных управляющих операторов.
• Операторы выбора, которые выбирают одну из двух или нескольких альтернативных последовательностей выполнения: условные операторы (if) и переключатели (case или switch).
• Операторы цикла, в которых повторяется выполнение последовательности операторов: операторы for и while.
Хорошее понимание циклов особенно важно по двум причинам: 1) большая часть времени при выполнении будет (очевидно) потрачена на циклы, и 2) многие ошибки связаны с неправильным кодированием начала или конца цикла.
2.7. Подпрограммы
Подпрограмма — это программный сегмент, состоящий из объявлений данных и исполняемых операторов, которые можно неоднократно вызывать (call) из различных частей программы. Подпрограммы называются процедурами (procedure), функциями (function), подпрограммами (subroutine) или методами (method). Первоначально они использовались только для того, чтобы разрешить многократное использование сегмента программы. Современная точка зрения состоит в том, что подпрограммы являются важным элементомструктуры программы и что каждый сегмент программы, который реализует некоторую конкретную задачу, следует оформить как отдельную подпрограмму.
При вызове подпрограммы ей передается последовательность значений, называемых параметрами. Параметры используются, чтобы задать различные варианты выполнения подпрограммы, передать ей данные и получить результаты вычисления. Механизмы передачи параметров сильно различаются в разных языках, и программисты должны хорошо понимать их воздействие на надежность и эффективность программы.
2.8. Модули
Тех элементов языка, о которых до сих пор шла речь, достаточно для написания программ, но недостаточно для написания программной системы: очень большой программы или набора программ, разрабатываемых группами программистов. Студенты часто на основе своих успехов в написании (небольших) программ заключают, что точно так же можно писать программные системы, но горький опыт показал, что написание большой системы требует дополнительных методов и инструментальных средств, выходящих за рамки простого программирования. Термин проектирование программного обеспечения (software engineering) используется для обозначения методов и инструментальных средств, предназначенных для проектирования, конструирования и управления при создании программных систем. В этой книге мы ограничимся обсуждением поддержки больших систем, которую можно осуществить с помощью языков программирования.
Возможно, вам говорили, что отдельная подпрограмма не должна превышать 40 или 50 строк, потому что программисту трудно читать и понимать большие сегменты кода. Согласно тому же критерию, должно быть понятно взаимодействие 40 или 50 подпрограмм. Отсюда следует очевидный вывод: любую программу, в которой больше 1600 — 2500 строк, трудно понять! Так как в полезных программах могут быть десятки тысяч строк и нередки системы из сотен тысяч строк, то очевидно, что необходимы дополнительные структуры для создания больших систем.
При использовании старых языков программирования единственным выходом были «бюрократические» методы: наборы правил и соглашений, которые предписывают членам группы, как следует писать программы. Современные языки программирования предлагают еще один метод структурирования для инкапсуляции данных и подпрограмм в более крупные объекты, называемые модулями. Преимущество модулей над бюрократическими предписаниями в том, что согласование модулей можно проверить при компиляции, чтобы предотвратить ошибки и недоразумения. Кроме того, фактически выполнимые операторы и большинство данных модуля (или все) можно скрыть таким образом, чтобы их нельзя было изменять или использовать, за исключением тех случаев, которые определены интерфейсом.
Есть две потенциальные трудности применения модулей на практике.
• Необходима мощная среда, разработки программ, чтобы отслеживать «истории», модулей и проверять интерфейсы. ;
• Разбиение на модули поощряет использование большого числа небольших подпрограмм с соответствующим увеличением времени выполнения из-за накладных расходов на вызовы подпрограмм.
Однако это больше не является проблемой: ресурсов среднего персонального компьютера более чем достаточно для поддержки среды языков C++ или Ada, а современная архитектура вычислительной системы и методы компиляции минимизируют издержки обращений.
Тот факт, что язык поддерживает модули, не помогает нам решать, что именно включить в модуль. Другими словами, остается вопрос, как разбить программную систему на модули? Поскольку качество системы непосредственно зависит от качества декомпозиции, компетентность разработчика программ должна оцениваться по способности анализировать требования проекта и создавать самую лучшую программную структуру для его реализации. Требуется большой опыт, чтобы развить эту способность. Возможно, самый лучший способ состоит в том, чтобы изучать существующие системы программного обеспечения.
Несмотря на тот факт, что невозможно научить здравому смыслу в проектировании программ, есть некоторые принципы, которые можно изучить. Одним из основных методов декомпозиции программы является объектно-ориентированное программирование (ООП), опирающееся на концепцию типа, рассмотренную выше. Согласно ООП, модуль следует создавать для любого реального или абстрактного «объекта», который может представляться набором данных и операций над этими данными. В главах 14 и 15 детально обсуждается языковая поддержка ООП.
2.9. Упражнения
1. Переведите синтаксис (отдельные фрагменты) языков С или Ada из нормальной формы Бекуса — Наура в синтаксические диаграммы.
2. Напишите программу на языке Pascal или С, которая компилируется и выполняется, но вычисляет неправильный результат из-за незакрытого комментария.
3. Даже если бы язык Ada использовал стиль комментариев, как в языках С и Pascal, то ошибки, вызванные незакрытыми комментариями, были бы менее частыми, Почему?
4. В большинстве языков ключевые слова, подобные begin и while, зарезервированы и не могут использоваться как идентификаторы. В других языках типа Fortran и PL/1 нет зарезервированных ключевых слов. Каковы преимущества и недостатки зарезервированных слов?
Глава 3
Среды программирования
Язык — это набор правил для написания программ, которые являются всего лишь последовательностями символов. В этой главе делается обзор компонентов среды программирования — набора инструментов, используемых для преобразования символов в выполнимые вычисления.
Редактор - это, инструментальное средство для создания и изменения исходных файлов, которые являются символьными файлами, содержащими написанную на языке программирования программу.
Компилятор транслирует символы из исходного файла в объектной модуль, который содержит команды в машинном коде для конкретного комью-тера.
Библиотекарь поддерживает совокупности объектных файлов, называемые библиотеками.
Компоновщик, или редактор связей, собирает объектные файлы отдельных компонентов программы и разрешает внешние ссылки от одного компонента к другому, формируя исполняемый файл.