И.Г. Головин, И.А. Волкова - Языки и методы программирования (1160773), страница 3
Текст из файла (страница 3)
Далее объявляется функция main, с вызова которой начинаетсявыполнение программы на С. В теле этой функции объявлены переменные current (для ввода очередного элемента последовательности)и count (для хранения числа элементов). Следующий далее операторцикла while соответствует подготовительному шагу и вводит в массив Input всю входную последовательность. Функция getchar вводитодин символ из входной последовательности.Заметим, что в языке С присваивание «=» является операцией.Эта операция выполняется так же, как и оператор присваиванияв классических ИЯП, но отличается от последнего тем, что присвоенное значение является одновременно и значением операцииприсваивания.
Таким образом, операция присваивания не тольковычисляет значение (как и любая другая операция), но и меняетзначение переменной из своей левой части. Такие операции называются операциями с побочным эффектом. Как и другие, операцияприсваивания может комбинироваться с другими операциями в выражении.В нашем примере выражение в заголовке цикла(current = getcharO) !=EOFвызывает функцию getchar и затем присваивает ее значение переменной current, после чего сравнивает это же значение (т. е. введенныйсимвол) с признаком конца ввода EOF (заметим, что EOF — это неособое значение символа, а лишь признак конца, вырабатываемыйдрайвером ввода операционной системы).
Если значение введенногосимвола не совпадает с признаком конца, то цикл продолжается.Отметим, что операция присваивания — это не единственнаяоперация с побочным эффектом. Так операция ++ в выраженииcount++ обладает побочным эффектом, состоящим в увеличениина 1 значения переменной count, а само значение операции равнозначению count до выполнения этой операции. Поэтому оператор11Input[count++] = current;как присваивает значение current очередному элементу массива, таки увеличивает значение счетчика count на 1.Аналогично операция — в выражении i— уменьшает значение iна единицу.В случае если символов слишком много, то в специальный каналвывода сообщений об ошибках (stderr) функция fprintf выводит текст "Слишком много символов".
После этого выполнениепрограммы завершается оператором return ] (возврат из функцииmain).Последний цикл for выводит элементы последовательности, начиная с конца. Так как индексы элементов массива всегда начинаютсяс 0, то последний введенный элемент будет иметь индекс count-1, апервый — 0, поэтому параметр цикла i последовательно уменьшаетсяс count-1 до 0.Последний оператор программы — возврат из функции main.Объектная парадигмаОбъектная парадигма основана на понятии объекта. Объектобладает состоянием и поведением.
Поведение состоит в посылкесообщений себе и другим объектам. Для каждого вида сообщения существуют «обработчики», которые могут модифицировать состояниеобъекта и посылать сообщения другим объектам. Объекты с одинаковым поведением и набором состояний объединяются в классы.Между классами могут существовать следующие отношения:• включение — «объект—подобъект» — включение объекта класса Xв объект другого класса Y, т.е. говоря т, ч то объект класса Y владеетобъектом класса X;• наследование — «суперкласс — подкласс» — объект подклассаDerived обладает всеми свойствами объекта суперкласса Base, атакже, возможно, дополнительными свойствами (специфичнымидля класса Derived).
Таким образом, все объекты класса Derivedодновременно принадлежат и классу Base, по не наоборот;• ссылка — объект класса w содержит (но не владеет) ссылку наобъект класса Ref.Также существуют и другие отношения.Объектная парадигма достаточно просто сочетается с императивной парадигмой. Состояние описывается набором переменных, аобработчики сообщений представляют собой процедуры или функции, имеющие доступ к состоянию.
Посылка сообщения сводится квызову соответствующего обработчика.В результате большинство современных языков индустриальногопрограммирования сочетает в себе обе парадигмы. Мы будем говорить об объектно-императивной парадигме программирования.12Одно их основных достоинств объектного подхода — это возможность создания достаточно гибких и универсальных иерархийклассов, которые могут быть использованы во многих прикладныхзадачах почти без изменения.
Неслучайно все индустриальныеобъектно-ориентированные языки программирования (ООЯП)включают в себя большой набор классов из стандартных библиотек.В случае если этих классов недостаточно, то ООЯП позволяют сравнительно легко (по сравнению с чисто императивной парадигмой)разрабатывать специализированные классы либо «с нуля», либо наоснове стандартных классов.Посмотрим, как на объектно-ориентированных языках решаетсязадача о реверсировании входной последовательности. Сначала рассмотрим решение на языке С#.Все рассуждения из предыдущего пункта по ходу решения задачи остаются справедливыми и здесь (ведь объектная парадигма вязыке C# расширяет, но не отменяет императивную).
Отличие объектного подхода в том, что здесь уже имеются готовые классы, позволяющие быстро решить поставленную задачу. В языке C# есть нетолько понятие массива, но и набор контейнеров как универсальных(вектор, список и т.д.), так и специализированных (динамическаястрока произвольной длины). Используем строки и массивы языкаС#:using System;class Program{static void Main(string[] args){string s = Console.In.ReadToEnd();char[] seq = s .ToCharArray();Array.Reverse(seq) ;Console.Write(seq);}}Первая строка программы сообщает об использовании стандартной библиотеки System, в которой нам понадобятся классы Consoleдля ввода-вывода и Array для операций с массивами.Далее следует объявление класса Program, содержащего описаниеединственной функции Main.
Эта функция играет такую же роль, чтоодноименная функция в языке С: с ее вызова начинается выполнениеконсольных программ.В первой строке мы объявляем строку s и сразу же вводим в неецеликом всю входную последовательность. Класс Console обладаетобъектом In класса TextReader, представляющим собой стандартный канал ввода. Объекты этого класса позволяют ввести целикомвсю входную последовательность. Заметим, что мы уже не нуждаемся13во введении ограничения на максимальную длину входной строки.Объем ввода лимитируется только размерами свободной виртуальнойпамяти, доступной процессу.Далее из введенной строки с помощью функции ToCharArrayконструируется массив из символов (char [] seq), составляющихстроку.
Функция Reverse из класса Array обращает массив (т.е.решает нашу задачу). После чего осталось только вывести реверсированный массив.Теперь можно сделать несколько очевидных выводов. Во-первых,объектное решение и проще для понимания, и короче. Во-вторых,объектное решение позволяет обрабатывать последовательностибольшей длины. Конечно, мы можем и в первом решении на языкеС отказаться от ограничения и использован, либо динамическиймассив, либо двунаправленный список и тому подобное. Однакотакое решение существенно длиннее и сложнее для понимания, чемпростой вариант.
Причина в том, что в императивных языках типа Сдостаточно сложно создавать гибкие и одновременно универсальныеконтейнеры. В каждом конкретном случае приходится создавать такиесущности с нуля. Это одна из причин популярности ООЯП. При программировании в объектном стиле существенно проще использоватьуже готовые объекты. Да и создавать новые объекты проще, чем вимперативном языке.Однако императивный стиль программирования тоже обладаетнекоторыми достоинствами. Например, в приведенной программена языке C# не совсем очевиден тот факт, что оперативная памятьиспользуется в ней расточительно.
Входная последовательностьхранится в двух экземплярах: в строке s первый экземпляр, а в массиве seq — второй. Конечно, первое решение все равно хуже, но вимперативном стиле можно разработать довольно сложное решение,которое будет работать с памятью лучше, чем объектное решение наязыке С#. Однако легкость программирования на ООЯП в большинстве случаев перевешивает.
Кроме того, и па языке типа C# можнопрограммировать в императивном стиле. И пашем примере можноиспользовать тот факт, что строки, как и массивы, допускают индексирование, и сделать вывод в стиле языка С:using System;class Program{static void M ain(string[] args){string s = Console.In.ReadToEnd() ;for (int i = s.Length-1; i>=0;i— >Console.Write (s [i]);}14}Заметим, что некоторые ООЯП, например C++, в дополнение кклассическому объектному механизму обладают весьма мощными ивыразительными средствами обобщенного программирования.Функциональная парадигмаОсновные понятия функциональных языков — функция и выражение.
Выражение — это комбинация вызовов функций. Наряду с большим числом стандартных (встроенных в язык) функций программист может определять свои функции. Определениеновой функции включает в себя имя функции, список аргументови выражение—тело функции. Вызов функции состоит из именифункции и списка выражений — фактических параметров.
Каждыйиз фактических параметров соответствует аргументу в определениифункции.Основная операция — вызов функции. При вызове функциисначала вычисляются выражения — фактические параметры, а затемих значения подставляются вместо соответствующих им аргументовв выражение—тело функции. Наконец, вычисляется значение тела,которое и будет значением вызова.Приведем одно из решений нашей задачи на языке Лисп [34] —первом языке программирования, в котором была реализованафункциональная парадигма.Будем использовать один из самых популярных диалектов Лиспа — Коммон Лисп.Прежде чем рассматривать решение, сделаем несколько замечаний о представлении программ на языке Лисп. Вызов функцииимеет вид(имя-функции список-фактических-параметров)Например,(+2 3)Определение функции имеет вид(defun имя-функции (список-имен-аргументов) выражение)Например,(defun plusl (х) (+ х 1))Обращение к стандартной функции ввода следующее:(read)Однако эта функция возвращает не последовательность литер, аболее сложную структуру — Лисп-выражение.