Алгоритм по информатике примеры и блок схемы

Алгоритм по информатике примеры и блок схемы
Алгоритм по информатике примеры и блок схемы
Алгоритм по информатике примеры и блок схемы
Алгоритм по информатике примеры и блок схемы
Алгоритм по информатике примеры и блок схемы

 

Парадигмы программирования

Обзор парадигм программирования.

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

Закономерности в процессе реализационного освоения новых областей обработки информации.

Материал из Википедии — свободной энциклопедии

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

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

История термина

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

Термин «парадигма программирования» впервые применил Роберт Флойд в своей лекции лауреата премии Тьюринга.

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

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

Таким образом, по мнению Роберта Флойда, в отличие от парадигм в научном мире, описанных Куном, парадигмы программирования могут сочетаться, обогащая инструментарий программиста.

Основные модели программирования Подходы и приёмы

 

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

Императивные языки программирования противопоставляются функциональным и логическим языкам программирования. Функциональные языки, например, Haskell, не представляют собой последовательность инструкций и не имеют глобального состояния. Логические языки программирования, такие как Prolog, обычно определяют что надо вычислить, а не как это надо делать.

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

Замечания. Следует отличать функции от подпрограмм в процедурном программировании. Противопоставляется парадигме императивного программирования, которая описывает процесс вычислений как последовательность изменения состояний (в значении, подобном таковому в теории автоматов). Функциональное программирование не предполагает изменяемость данных (в отличие от императивного, где одной из базовых концепций является переменная).

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

Наиболее известными языками функционального программирования являются:

Haskell — чистый функциональный. Назван в честь Хаскелла Карри. LISP (Джон МакКарти, 1958, множество его потомков, наиболее современные из которых — Scheme и Common Lisp). ML (Робин Милнер, 1979, из ныне используемых диалектов известны Standard ML и Objective CAML). Miranda (Дэвид Тёрнер, 1985, который впоследствии дал развитие языку Haskell). Erlang — (Joe Armstrong, 1986) функциональный язык с поддержкой процессов. Nemerle — гибридный функционально/императивный язык.

Еще не полностью функциональные изначальные версии и Lisp и APL внесли особый вклад в создание и развитие функционального программирования. Более поздние версии Lisp, такие как Scheme, а так же различные варианты APL поддерживали все свойства и концепции функционального языка.

Как правило, интерес к функциональным языкам программирования, особенно чисто функциональным, был сугубо научный, нежели коммерческий. Однако, таким примечательным языкам как Erlang, OCaml, Haskell, Scheme (после 1986) а так же специфическим R (статистика), Mathematica (символическая математика), J и K (финансовый анализ), и XSLT (XML) находили применение в индустрии коммерческого программирования. Такие широко распространенные декларативные языки как SQL и Lex/Yacc содержат некоторые элементы функционального программирования, они остерегаются использовать переменные. Языки работы с электронными таблицами также можно рассматривать как функциональные.

Многие нефункциональные языки, такие как C, C++ и C# могут вести себя как функциональные при использовании указателей на функцию, в соответствие с библиотекой <functional>.

Ля?мбда-исчисле?ние (?-исчисление, лямбда-исчисление) — формальная система, разработанная американским математиком Алонзо Чёрчем, для формализации и анализа понятия вычислимости.

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

?-исчисление реализовано Джоном Маккарти в языке Лисп. В начале реализация идеи ?-исчисления была весьма громоздкой. Но по мере развития Лисп-технологии (прошедшей этап аппаратной реализации в виде Лисп-машины) идеи получили ясную и четкую реализацию.

Чистое ?-исчисление

Это простейший из семейства прототипных языков программирования, чистое ?-исчисление, термы которого, называемые также объектами (обами), или ?-термами, построены исключительно из переменных применением аппликации и абстракции. Изначально наличия каких-либо констант не предполагается.

Аппликация и абстракция

В основу ?-исчисления положены две фундаментальные операции: аппликация и абстракция. Аппликация означает применение или вызов функции по отношению к заданному значению. Её обычно обозначают f a, где f — функция, а a — значение. Это соответствует общепринятой в математике записи f(a), которая тоже иногда используется, однако для ?-исчисления важно то, что f трактуется как алгоритм, вычисляющий результат по заданному входному значению. В этом смысле аппликация f к a может рассматриваться двояко: как результат применения f к a, или же как процесс вычисления f a. Последняя интерпретация аппликации связана с понятием ?-редукции.

Абстракция или ?-абстракция в свою очередь строит функции по заданным выражениям. Именно, если t\equiv t[x]  — выражение, свободно содержащее x, тогда запись ?x.t[x] означает: ? функция от аргумента x, которая имеет вид t[x]) обозначает функцию x\mapsto t[x] . Таким образом, с помощью абстракции можно конструировать новые функции. Требование, чтобы x свободно входило в t, не очень существенно — достаточно предположить, что \lambda x.t\equiv t , если это не так.

?-редукция

Поскольку выражение \lambda x. 2\cdot x + 1 обозначает функцию, ставящую в соответствие каждому x значение 2\cdot x + 1 , то для вычисления выражения

(\lambda x. 2\cdot x + 1)3

в которое входят и аппликация и абстракция, необходимо выполнить подстановку числа 3 в терм 2\cdot x + 1 вместо переменной x. В результате получается 2\cdot 3+1=7 . Это соображение в общем виде записывается как

(?x.t) a = t[x:=a]

и носит название ?-редукция. Выражение вида (?x.t) a, то есть применение абстракции к некому терму, называется редексом (redex). Несмотря на то, что ?-редукция по сути является единственной «существенной» аксиомой ?-исчисления, она приводит к весьма содержательной и сложной теории. Вместе с ней ?-исчисление обладает свойством полноты по Тьюрингу и, следовательно, представляет собой простейший язык программирования.

?-преобразование

?-преобразование выражает ту идею, что две функции являются идентичными тогда и только тогда, когда, будучи применённые к любому аргументу, дают одинаковые результаты. ?-преобразование переводит друг в друга формулы ?x.fx и f (в обратную сторону — только если x не имеет свободных вхождений в f: иначе свободная переменная x после преобразования станет связанной внешней абстракцией).

Функция двух переменных x и y f(x,y) = x + y может быть рассмотрена как функция одной переменной x, возвращающая функцию одной переменной y, то есть как выражение

?x.?y.x+y

Такой приём работает точно так же для функций любой арности. Это показывает, что функции многих переменных могут быть без проблем выражены в ?-исчислении и являются «синтаксическим сахаром». Описанный процесс превращения функций многих переменных в функцию одной переменной называется карринг (также: каррирование), в честь американского математика Хаскелла Карри, хотя первым его предложил М. И. Шейнфинкель (1924).

Семантика бестипового ?-исчисления

Тот факт, что термы ?-исчисления действуют как функции, применяемые к термам ?-исчисления (то есть, возможно, к самим себе), приводит к сложностям построения адекватной семантики ?-исчисления. Можно ли приписать ?-исчислению какой-либо смысл? Желательно иметь множество D, в которое вкладывалось бы его пространство функций D > D. В общем случае такого D не существует по соображениям ограничений на мощности этих двух множеств, D и функций из D в D: второе имеет большую мощность, чем первое.

Эту трудность преодолел Д. С. Скотт, построив понятие области D (полной решётки[1] или, более обще, полного частично упорядоченного множества со специальной топологией) и урезав D > D до непрерывных (в имеющейся топологии) функций[2]. После этого также стало понятно, как можно строить денотационную семантику языков программирования. Это произошло благодаря тому, что с помощью конструкций Скотта можно придать значение также двум важным конструкциям языков программирования — рекурсии и типам данных.

Связь с рекурсивными функциями В языках программирования

В языках программирования под «?-исчислением» зачастую понимается механизм «анонимных функций» — callback-функций, которые можно определить прямо в том месте, где они используются, и которые имеют доступ к локальным переменным текущей функции.

См. также Ссылки ^ Scott D.S. The lattice of flow diagrams.-- Lecture Notes in Mathematics, 188, Symposium on Semantics of Algorithmic Languages.-- Berlin, Heidelberg, New York: Springer-Verlag, 1971, pp. 311—372. ^ Scott D.S. Lattice-theoretic models for various type-free calculi. — In: Proc. 4th Int. Congress for Logic, Methodology, and the Philosophy of Science, Bucharest, 1972. Литература Барендрегт X. Ламбда-исчисление. Его синтаксис и семантика: Пер. с англ. — М.: Мир, 1985. — 606 с. Концепции

Некоторые концепции и парадигмы специфичны для функционального программирования и в основном чужды императивному программированию (включая объектно-ориентированное программирование). Тем не менее, языки программирования обычно представляют собой гибрид нескольких парадигм программирования, поэтому «большей частью императивные» языки программирования могут использовать какие-либо из этих концепций.

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

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

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

Чистые функции

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

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

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

До тех пор пока большинство компиляторов императивных языков программирования распознают чистые функции и удаляют общие подвыражения для вызовов чистых функций, они не смогут делать это всегда для предварительно скомпилированных библиотек, которые, как правило, не предоставляют эту информацию. Некоторые компиляторы, такие как gcc, в целях оптимизации предоставляют программисту ключевые слова для обозначения чистых функций. Fortran 95 позволяет обозначать функции как “pure” (чистые).

Рекурсия

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

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

Пример на Scheme:

(define (factorial n) (define (fac-times n acc) (if (= n 0) acc (fac-times (- n 1) ( acc n)))) (if (< n 0) (display "Неправильный параметр!") (fac-times n 1)))

Пример на Scala:

def factorial(x: BigInt) = { def f2(x: BigInt, sum: BigInt): BigInt = if (x == 1) sum else f2(x - 1, x sum) f2(x, 1) }

Рекурсия — метод определения класса объектов или методов предварительным заданием одного или нескольких (обычно простых) его базовых случаев или методов, а затем заданием на их основе правила построения определяемого класса, ссылающегося прямо или косвенно на эти базовые случаи.

Другими словами, рекурсия — способ общего определения объекта или действия через себя, с использованием ранее заданных частных определений. Рекурсия используется, когда можно выделить самоподобие задачи.

Определение в логике, использующее рекурсию, называется индуктивным (см., например, Натуральное число).

Примеры Метод Гаусса — Жордана для решения Системы линейных алгебраических уравнений является рекурсивным. Факториал целого неотрицательного числа n обозначается n! и определяется как n!=n(n-1) при n > 0 и n! = 1 при n = 0 Числа Фибоначчи определяются с помощью рекуррентного соотношения: Первое и второе числа Фибоначчи равны 1 Для n > 2, n ? e число Фибоначчи равно сумме (n ? 1)-го и (n ? 2)-го чисел Фибоначчи Практически все геометрические фракталы задаются в форме бесконечной рекурсии. (например, треугольник Серпинского). Задача «Ханойские башни». Её содержательная постановка такова: В одном из буддийских монастырей монахи уже тысячу лет занимаются перекладыванием колец. Они располагают тремя пирамидами, на которых надеты кольца разных размеров. В начальном состоянии 64 кольца были надеты на первую пирамиду и упорядочены по размеру. Монахи должны переложить все кольца с первой пирамиды на вторую, выполняя единственное условие — кольцо нельзя положить на кольцо меньшего размера. При перекладывании можно использовать все три пирамиды. Монахи перекладывают одно кольцо за одну секунду. Как только они закончат свою работу, наступит конец света. Рекурсивный вариант решения задачи можно описать так:

Алгоритм по передвижению башни, алгоритм передвинет нужное количество дисков из пирамиды «источник» на пирамиду «задание» используя «запасную» пирамиду.

Если число дисков равно одному, тогда:

Передвиньте диск из источника в задание

В противном случае:

Рекурсивно передвиньте все диски кроме одного из источника в запас, используя задание как запас Передвиньте оставшийся диск из источника в задание Передвиньте все диски из запаса в задание используя источник как запас Рекурсия в программировании Функции

В программировании рекурсия — вызов функции (процедуры) из неё же самой, непосредственно (простая рекурсия) или через другие функции (сложная рекурсия), например, функция A вызывает функцию B, а функция B — функцию A. Количество вложенных вызовов функции или процедуры называется глубиной рекурсии.

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

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

Впрочем, имеется специальный тип рекурсии, называемый «хвостовой рекурсией». Интерпретаторы и компиляторы функциональных языков программирования, поддерживающие оптимизацию кода (исходного и/или исполняемого), автоматически преобразуют хвостовую рекурсию к итерации, благодаря чему обеспечивают выполнение алгоритмов с хвостовой рекурсией в ограниченном объёме памяти. Такие рекурсивные вычисления, даже если они формально бесконечны (например, когда с помощью рекурсии организуется работа командного интерпретатора, принимающего команды пользователя), никогда не приводят к исчерпанию памяти. К сожалению, далеко не всегда стандарты языков программирования чётко определяют, каким именно условиям должна удовлетворять рекурсивная функция, чтобы транслятор гарантированно преобразовал её в итерацию. Одно из редких исключений — язык Scheme (диалект языка Lisp), описание которого содержит все необходимые сведения.

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

Данные

Описание типа данных может содержать ссылку на саму себя. Подобные структуры используются при описании списков и графов. Пример описания списка (C++):

class element_of_list { element_of_list next; / ссылка на следующий элемент того же типа / int data; / некие данные / };

Рекурсивная структура данных зачастую обуславливает применение рекурсии для обработки этих данных.

Рекурсия в физике

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

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

Рекурсия в лингвистике

Способность языка порождать вложенные предложения и конструкции. Базовое предложение кошка съела мышь может быть за счет рекурсии расширено как Ваня догадался, что кошка съела мышь, далее как Катя знает, что Ваня догадался, что кошка съела мышь и так далее. Рекурсия считается одной из лингвистических универсалий, то есть свойственна любому естественному языку (хотя в последнее время активно обсуждается возможное отсутствие рекурсии в одном из языков Амазонии — пираха, которое отмечает лингвист Д. Эверетт). О рекурсии в лингвистике, ее разновидностях и наиболее характерных проявлениях в русском языке описано в статье Е.А.Лодатко "Рекурсивные лингвистические структуры" (см.: Рекурсивные лингвистические структуры)

Цитаты

Итерация от человека. Рекурсия — от Бога. — Л. Питер Дойч[1]

Юмор

Большая часть всех шуток о рекурсии касается бесконечной рекурсии, в которой нет условия выхода. Известное высказывание: Чтобы понять рекурсию, нужно сначала понять рекурсию' или 'Чтобы что-то сделать, надо что-то сделать. Весьма популярна шутка о рекурсии, напоминающая словарную статью:

рекурсия  см. рекурсия

Несколько рассказов Станислава Лема посвящены (возможным) казусам при бесконечной рекурсии:

Рассказ про Йона Тихого «Путешествие четырнадцатое» из «Звёздных дневников Ийона Тихого», в котором герой последовательно переходит от статьи о сепульках к статье о сепуляции, оттуда к статье о сепулькариях, в которой снова стоит отсылка к статье «сепульки». Рассказ о разумной машине, которая обладала достаточным умом и ленью, чтобы для решения поставленной задачи построить себе подобную, и поручить решение ей (итогом стала бесконечная рекурсия, когда каждая новая машина строила себе подобную и передавала задание ей).

Русская народная сказка-песня «У попа была собака…» являет пример рекурсии:

У попа была собака, он её любил,
Она съела кусок мяса, он её убил,
В землю закопал,
Надпись написал:

"У попа была собака, он её любил, Она съела кусок мяса, он её убил, В землю закопал, Надпись написал: "У попа была собака, он её любил, Она съела кусок мяса, он её убил, В землю закопал, Надпись написал: …


См. также Подход к вычислению аргументов

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

print length([2+1, 32, 1/0, 5-4])

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

Как правило нестрогий подход реализуется в виде редукции графа. Нестрогое вычисление используется по умолчанию в нескольких чисто функциональных языках, в том числе Miranda, Clean и Haskell.

Функциональное программирование в нефункциональных языках

Принципиально нет препятствий для написания программ в функциональном стиле на языках, которые традиционно не считаются функциональными (точно так же, как программы в объектном стиле можно писать на обычных структурных языках). Некоторые императивные языки поддерживают типичные для функциональных языков конструкции, такие как функции высшего порядка и дополнение списков (list comprehensions), что облегчает использование функционального стиля в этих языках.

В языке C указатели на функцию могут быть использованы для получения эффекта функций высшего порядка. Функции высшего порядка и отложенная списковая структура реализованы в библиотеках С++. В сложных языках, типа Алгол-68, имеющиеся средства метапрограммирования (фактически - дополнения языка новыми конструкциями) позволяют создать специфичные для функционального стиля объекты данных и программные конструкции, после чего можно писать функциональные программы с их использованием.

Стили программирования

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

# imperative style target = [] # create empty list for item in source_list: # iterate over each thing in source trans1 = G(item) # transform the item with the G() function trans2 = F(trans1) # second transform with the F() function target.append(trans2) # add transformed item to target

Функциональная версия выглядит по-другому

# functional style # FP-oriented languages often have standard compose() compose2 = lambda A, B: lambda x: A(B(x)) target = map(compose2(F, G), source_list)

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

Особенности

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

Сильные стороны Повышение надёжности кода

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

Удобство организации модульного тестирования

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

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

Возможности оптимизации при компиляции

Традиционно упоминаемой положительной особенностью функционального программирования является то, что оно позволяет описывать программу в так называемом «декларативном» виде, когда жесткая последовательность выполнения многих операций, необходимых для вычисления результата, в явном виде не задаётся, а формируется автоматически в процессе вычисления функций.[источник?] Это обстоятельство, а также отсутствие состояний даёт возможность применять к функциональным программам достаточно сложные методы автоматической оптимизации.

Возможности параллелизма

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

Недостатки

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

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

Примечания А. Филд, П. Харрисон Функциональное программирование: Пер. с англ. — М.: Мир, 1993. — 637 с., ил. ISBN 5-03-001870-0. Стр. 120 [Глава 6: Математические основы: ?-исчисление]. Н. А. Роганова Функциональное программирование: Учебное пособие для студентов высших учебных заведений — М.: ГИНФО, 2002. — 260 с. Стр. 14 п. 3.1. Ленивые и энергичные вычисления Ахмечет В. «Функциональное программирование для всех» Литература Городняя Л. В. Основы функционального программирования. Курс лекций — М.: Интернет-университет информационных технологий, 2004. С. 280. ISBN 5-9556-0008-6 Душкин Р. В. Функциональное программирование на языке Haskell. — М.: ДМК Пресс, 2006. С. 608. ISBN 5-94074-335-8 А. Филд, П. Харрисон Функциональное программирование: Пер. с англ. — М.: Мир, 1993. — 637 с., ил. ISBN 5-03-001870-0 Н. А. Роганова Функциональное программирование: Учебное пособие для студентов высших учебных заведений — М.: ГИНФО, 2002. — 260 с.

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

Первым языком логического программирования был язык Planner, в котором была заложена возможность автоматического вывода результата из данных и заданных правил перебора вариантов (совокупность которых называлась планом). Planner использовался для того, чтобы понизить требования к вычислительным ресурсам (с помощью метода backtracking) и обеспечить возможность вывода фактов, без активного использования стека. Затем был разработан язык Prolog, который не требовал плана перебора вариантов и был, в этом смысле, упрощением языка Planner.

От языка Planner также произошли логические языки программирования QA-4, Popler, Conniver, и QLISP. Языки программирования Mercury, Visual Prolog, Oz и Fril произошли уже от языка Prolog. На базе языка Planner было разработано также несколько альтернативных языков логического программирования, не основанных на методе backtracking, например, Ether (см. обзор Шапиро [1989]).

Библиографические ссылки Иван Братко Алгоритмы искусственного интеллекта на языке PROLOG = Prolog Programming For Artificial Intelligence. — М.: «Вильямс», 2004. — С. 640. — ISBN 0-201-40375-7 John McCarthy. Programs with common sense Symposium on Mechanization of Thought Processes. National Physical Laboratory. Teddington, England. 1958. Fisher Black. A deductive question answering system Harvard University. Thesis. 1964. James Slagle. Experiments with a Deductive Question-Answering Program CACM. December, 1965. Cordell Green. Application of Theorem Proving to Problem Solving IJCAI 1969. Carl Hewitt. Planner: A Language for Proving Theorems in Robots IJCAI 1969. Gerry Sussman and Terry Winograd. Micro-planner Reference Manual AI Memo No, 203, MIT Project MAC, July 1970. Carl Hewitt. Procedural Embedding of Knowledge In Planner IJCAI 1971. Terry Winograd. Procedures as a Representation for Data in a Computer Program for Understanding Natural Language MIT AI TR-235. January 1971. Bruce Anderson. Documentation for LIB PICO-PLANNER School of Artificial Intelligence, Edinburgh University. 1972 Bruce Baumgart. Micro-Planner Alternate Reference Manual Stanford AI Lab Operating Note No. 67, April 1972. Julian Davies. Popler 1.6 Reference Manual University of Edinburgh, TPU Report No. 1, May 1973. Jeff Rulifson, Jan Derksen, and Richard Waldinger. QA4, A Procedural Calculus for Intuitive Reasoning SRI AI Center Technical Note 73, November 1973. Robert Kowalski Predicate Logic as Programming Language Memo 70, Department of Artificial Intelligence, Edinburgh University. 1973. Drew McDermott and Gerry Sussman. The Conniver Reference Manual MIT AI Memo 259A. January 1974. Earl Sacerdoti, et al. QLISP: A Language for the Interactive Development of Complex Systems AFIPS National Computer Conference. 1976. Bill Kornfeld and Carl Hewitt. The Scientific Community Metaphor IEEE Transactions on Systems, Man, and Cybernetics. January 1981. Bill Kornfeld. The Use of Parallelism to Implement a Heuristic Search IJCAI 1981. Bill Kornfeld. Parallelism in Problem Solving MIT EECS Doctoral Dissertation. August 1981. Bill Kornfeld. Combinatorially Implosive Algorithms CACM. 1982 Carl Hewitt. The Challenge of Open Systems Byte Magazine. April 1985. Robert Kowalski. The limitation of logic Proceedings of the 1986 ACM fourteenth annual conference on Computer science. Ehud Shapiro (Editor). Concurrent Prolog MIT Press. 1987. Robert Kowalski. The Early Years of Logic Programming CACM. January 1988. Ehud Shapiro. The family of concurrent logic programming languages ACM Computing Surveys. September 1989. Carl Hewitt and Gul Agha. Guarded Horn clause languages: are they deductive and Logical? International Conference on Fifth Generation Computer Systems, Ohmsha 1988. Tokyo. Also in Artificial Intelligence at MIT, Vol. 2. MIT Press 1991. Shunichi Uchida and Kazuhiro Fuchi Proceedings of the FGCS Project Evaluation Workshop Institute for New Generation Computer Technology (ICOT). 1992. Объектно-ориентированное программирование

Объектно-ориентированное программирование (ООП) — парадигма программирования, в которой основными концепциями являются понятия объектов и классов (либо, в менее известном варианте языков с прототипированием — прототипов).

Класс — это тип, описывающий устройство объектов. Понятие «класс» подразумевает некоторое поведение и способ представления. Понятие «объект» подразумевает нечто, что обладает определённым поведением и способом представления. Говорят, что объект — это экземпляр класса. Класс можно сравнить с чертежом, согласно которому создаются объекты. Обычно классы разрабатывают таким образом, чтобы их объекты соответствовали объектам предметной области.

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

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

Прототип — это объект-образец, по образу и подобию которого создаются другие объекты.

История

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

Первым языком программирования, в котором были предложены принципы объектной ориентированности, была Симула. В момент своего появления (в 1967 году), этот язык программирования предложил поистине революционные идеи: объекты, классы, виртуальные методы и др., однако это всё не было воспринято современниками как нечто грандиозное. Тем не менее, большинство концепций были развиты Аланом Кэйем и Дэном Ингаллсом в языке Smalltalk. Именно он стал первым широко распространённым объектно-ориентированным языком программирования.

В настоящее время количество прикладных языков программирования , реализующих объектно-ориентированную парадигму, является наибольшим по отношению к другим парадигмам. В области системного программирования до сих пор применяется парадигма процедурного программирования, и общепринятым языком программирования является язык C. Хотя при взаимодействии системного и прикладного уровней операционных систем заметное влияние стали оказывать языки объектно-ориентированного программирования. Например, одной из наиболее распространенных библиотек мультиплатформенного программирования является объектно-ориентированная библиотека Qt, написанная на языке C++.

Главные понятия и разновидности

Структура данных «класс», представляющая собой объектный тип данных, внешне похожа на типы данных процедурно-ориентированных языков, такие как структура в языке Си или запись в Паскале или QuickBasic. При этом элементы такой структуры (члены класса) могут сами быть не только данными, но и методами (то есть процедурами или функциями).

Такое объединение называется инкапсуляцией.

Наличие инкапсуляции достаточно для объектности языка программирования, но ещё не означает его объектной ориентированности — для этого требуется наличие наследования.

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

Язык Self, соблюдая многие исходные положения объектно-ориентированного программирования, ввёл альтернативное классам понятие прототипа, положив начало прототипному программированию, считающемуся подвидом объектного.

Основные понятия Абстракция данных  Объекты представляют собою упрощенное, идеализированное описание реальных сущностей предметной области. Если соответствующие модели адекватны решаемой задаче, то работать с ними оказывается намного удобнее, чем с низкоуровневым описанием всех возможных свойств и реакций объекта. Инкапсуляция  Инкапсуляция — это принцип, согласно которому любой класс должен рассматриваться как чёрный ящик — пользователь класса должен видеть и использовать только интерфейсную часть класса (т. е. список декларируемых свойств и методов класса) и не вникать в его внутреннюю реализацию. Поэтому данные принято инкапсулировать в классе таким образом, чтобы доступ к ним по чтению или записи осуществлялся не напрямую, а с помощью методов. Принцип инкапсуляции (теоретически) позволяет минимизировать число связей между классами и, соответственно, упростить независимую реализацию и модификацию классов. Сокрытие данных  Сокрытие данных — неотделимая часть ООП, управляющая областями видимости. Является логическим продолжением инкапсуляции. Целью сокрытия является невозможность для пользователя узнать или испортить внутреннее состояние объекта. Наследование  Наследованием называется возможность порождать один класс от другого с сохранением всех свойств и методов класса-предка (прародителя, иногда его называют суперклассом) и добавляя, при необходимости, новые свойства и методы. Набор классов, связанных отношением наследования, называют иерархией. Наследование призвано отобразить такое свойство реального мира, как иерархичность. Полиморфизм  Полиморфизмом называют явление, при котором один и тот же программный код (полиморфный код) выполняется по-разному в зависимости от того, объект какого класса используется при вызове данного кода. Полиморфизм обеспечивается тем, что в классе-потомке изменяют реализацию метода класса-предка с обязательным сохранением сигнатуры метода. Это обеспечивает сохранение неизменным интерфейса класса-предка и позволяет осуществить связывание имени метода в коде с разными классами — из объекта какого класса осуществляется вызов, из того класса и берётся метод с данным именем. Такой механизм называется динамическим (или поздним) связыванием — в отличие от статического (раннего) связывания, осуществляемого на этапе компиляции . Определение ООП и его основные концепции Сложности определения

ООП имеет уже более чем сорокалетнюю историю, но, несмотря на это, до сих пор не существует чёткого общепринятого определения данной технологии. Основные принципы, заложенные в первые объектные языки и системы, подверглись существенному изменению (или искажению) и дополнению при многочисленных реализациях последующего времени. Кроме того, примерно с середины 1980-х годов термин «объектно-ориентированный» стал модным, в результате с ним произошло то же самое, что несколько раньше с термином «структурный» (ставшим модным после распространения технологии структурного программирования) — его стали искусственно «прикреплять» к любым новым разработкам, чтобы обеспечить им привлекательность. Бьёрн Страуструп в 1988 году писал, что обоснование «объектной ориентированности» чего-либо, в большинстве случаев, сводится к силлогизму: «X — это хорошо. Объектная ориентированность — это хорошо. Следовательно, X является объектно-ориентированным».

Тимоти Бадд пишет[1]:

Роджер Кинг аргументированно настаивал, что его кот является объектно-ориентированным. Кроме прочих своих достоинств, кот демонстрирует характерное поведение, реагирует на сообщения, наделён унаследованными реакциями и управляет своим, вполне независимым, внутренним состоянием.

Определение ООП

По мнению Алана Кея, создателя языка Smalltalk, которого считают одним из «отцов-основателей» ООП, объектно-ориентированный подход заключается в следующем наборе основных принципов (цитируется по вышеупомянутой книге Т. Бадда).

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

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

Устойчивость и управляемость системы обеспечивается за счёт чёткого разделения ответственности объектов (за каждое действие отвечает определённый объект), однозначного определения интерфейсов межобъектного взаимодействия и полной изолированности внутренней структуры объекта от внешней среды (инкапсуляции).

Концепции

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

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

Отдельного пояснения требует понятие обмена сообщениями. Первоначально (например, в том же Smalltalk) взаимодействие объектов представлялось как «настоящий» обмен сообщениями, то есть пересылка от одного объекта другому специального объекта-сообщения. Такая модель является чрезвычайно общей. Она прекрасно подходит, например, для описания параллельных вычислений с помощью активных объектов, каждый из которых имеет собственный поток исполнения и работает одновременно с прочими. Такие объекты могут вести себя как отдельные, абсолютно автономные вычислительные единицы. Посылка сообщений естественным образом решает вопрос обработки сообщений объектами, присвоенными полиморфным переменным — независимо от того, как объявляется переменная, сообщение обрабатывает код класса, к которому относится присвоенный переменной объект.

Однако общность механизма обмена сообщениями имеет и другую сторону — «полноценная» передача сообщений требует дополнительных накладных расходов, что не всегда удобно. Поэтому в большинстве ныне существующих объектно-ориентированных языков программирования используется концепция «отправка сообщения как вызов метода» — объекты имеют доступные извне методы, вызовами которых и обеспечивается взаимодействие объектов. Данный подход реализован в огромном количестве языков программирования, в том числе C++, Object Pascal, Java, Oberon-2. В настоящий момент именно он является наиболее распространённым в объектно-ориентированных языках

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

Особенности реализации

Как уже говорилось выше, в современных объектно-ориентированных языках программирования каждый объект является значением, относящимся к определённому классу). Класс представляет собой объявленный программистом составной тип данных, имеющий в составе:

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

Классы могут наследоваться друг от друга. Класс-потомок получает все поля и методы класса-родителя, но может дополнять их собственными либо переопределять уже имеющиеся. Большинство языков программирования поддерживает только единичное наследование (класс может иметь только один класс-родитель), но в C++ допускается множественное наследование — порождение класса от двух или более классов-родителей. Множественное наследование порождает целый ряд проблем, как логических, так и чисто реализацинных, поэтому в полном объёме его поддержка не распространена. Вместо этого в 1990-е годы появилось и стало активно вводиться в объектно-ориентированные языки понятие интерфейса. Интерфейс — это класс без полей и без реализации, включающий только заголовки методов. Если некий класс наследует (или, как говорят, реализует) интерфейс, он должен реализовать все входящие в него методы. Использование интерфейсов предоставляет относительно дешёвую альтернативу множественному наследованию.

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

Инкапсуляция обеспечивается следующими средствами

Контроль доступа  Поскольку методы класса могут быть как чисто внутренними, обеспечивающими логику функционирования объекта, так и внешними, с помощью которых взаимодействуют объекты, необходимо обеспечить скрытость первых при доступности извне вторых. Для этого в языки вводятся специальные синтаксические конструкции, явно задающие область видимости каждого члена класса. Традиционно это модификаторы public, protected и private, обозначающие, соответственно, открытые члены класса, члены класса, доступные только из классов-потомков и скрытые, доступные только внутри класса. Конкретная номенклатура модификаторов и их точный смысл различаются в разных языках. Свойства объекта  Псевдополя, доступные по чтению и/или записи через методы, называемые аксессорами (от англ. access — доступ). Эти методы часто называют геттерами (чтение) и сеттерами (запись). Они обеспечивают чтение и установку значений полей данных, связанных со свойствами. Свойства можно рассматривать как «умные» поля данных, которые сопровождают доступ к полю данных какими-либо дополнительными действиями (например, когда изменение координаты объекта сопровождается его перерисовкой на новом месте). Свойства, по сути — не более чем синтаксический сахар, поскольку никаких новых возможностей они не добавляют, а лишь скрывают вызов методов доступа.

Полиморфизм реализуется путём введения в язык правил, согласно которым переменной типа класс может быть присвоен объект любого класса-потомка её класса.

Подходы к проектированию программ в целом

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

Объектно-ориентированное проектирование основывается на описании структуры и поведения проектируемой системы, то есть, фактически, в ответе на два основных вопроса:

Из каких частей состоит система. В чём состоит ответственность каждой из частей.

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

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

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

Родственные методологии Компонентное программирование

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

Прототипное программирование

Прототипное программирование, сохранив часть черт ООП, отказалось от базовых понятий — класса и наследования.

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

Гради Буч указывает на следующие причины, приводящие к снижению производительности программ из-за использования объектно-ориентированных средств:

Динамическое связывание методов.  Обеспечение полиморфного поведения объектов приводит к необходимости связывать методы, вызываемые программой (то есть определять, какой конкретно метод будет вызываться) не на этапе компиляции, а в процессе исполнения программы, на что тратится дополнительное время. При этом реально динамическое связывание требуется не более чем для 20 % вызовов, но некоторые ООП-языки используют его постоянно. Значительная глубина абстракции.  ООП-разработка часто приводит к созданию «многослойных» приложений, где выполнение объектом требуемого действия сводится к множеству обращений к объектам более низкого уровня. В таком приложении происходит очень много вызовов методов и возвратов из методов, что, естественно, сказывается на производительности. Наследование «размывает» код.  Код, относящийся к «оконечным» классам иерархии наследования (которые обычно и используются программой непосредственно) — находится не только в самих этих классах, но и в их классах-предках. Относящиеся к одному классу методы фактически описываются в разных классах. Это приводит к двум неприятным моментам: Снижается скорость трансляции, так как компоновщику приходится подгружать описания всех классов иерархии. Снижается производительность программы в системе со страничной памятью — поскольку методы одного класса физически находятся в разных местах кода, далеко друг от друга, при работе фрагментов программы, активно обращающихся к унаследованным методам, система вынуждена производить частые переключения страниц. Инкапсуляция снижает скорость доступа к данным.  Запрет на прямой доступ к полям класса извне приводит к необходимости создания и использования методов доступа. И написание, и компиляция, и исполнение методов доступа сопряжено с дополнительными расходами. Динамическое создание и уничтожение объектов.  Динамически создаваемые объекты, как правило, размещаются в куче, что менее эффективно, чем размещение их на стеке и, тем более, статическое выделение памяти под них на этапе компиляции.

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

Критика ООП

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

Обычно сравнивают объектное и процедурное программирование:

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

Критические высказывания в адрес ООП:

Исследование Thomas E. Potok, Mladen Vouk и Andy Rindos [1] показало отсутствие значимой разницы в продуктивности разработки программного обеспечения между ООП и процедурным подходом. Кристофер Дэйт указывает на невозможность сравнения ООП и других технологий во многом из за отсутствия строгого и общепризнанного определения ООП (C. J. Date, Introduction to Database Systems, 6th-ed., Page 650) Александр Степанов, в одном из своих интервью, указывал на то, что ООП «методологически неправильно» и что «… ООП практически такая же мистификация как и искусственный интеллект…» ([2]). Фредерик Брукс (Frederick P. Brooks, Jr.) в своей статье «No Silver Bullet. Essence and Accidents of Software Engineering» (Computer Magazine; April 1987) указывает на то, что наиболее сложной частью создания программного обеспечения является « … спецификация, дизайн и тестирование концептуальных конструкций, а отнюдь не работа по выражению этих концептуальных конструкций…». ООП (наряду с такими технологиями как искусственный интеллект, верификация программ, автоматическое программирование, графическое программирование, экспертные системы и др), по его мнению, не является «серебряной пулей», которая может снизить сложность разработки программных систем. По его мнению «…ООП позволяет сократить только привнесённую сложность в выражение дизайна. Дизайн остаётся сложным по своей природе…». ([3]) Эдсгер Дейкстра указывал: «… то о чём общество в большинстве случаев просит — это змеиное масло. Естественно, „змеиное масло“ имеет очень впечатляющие имена, иначе будет очень трудно что-то продать: „Структурный анализ и Дизайн“, „Программная инженерия“, „Модели зрелости“, „Управляющие информационные системы“ (Management Information Systems), „Интегрированные среды поддержки проектов“, „Объектная ориентированность“, „Реинжиниринг бизнес-процессов“…» — EWD 1175: The strengths of the academic enterprise Никлаус Вирт считает, что ООП — не более чем тривиальная надстройка над структурным программированием, и преувеличение её значимости, выражающееся, в том числе, во включении в языки программирования всё новых модных «объектно-ориентированных» средств, вредит качеству разрабатываемого программного обеспечения. Патрик Киллелиа в своей книге «Тюнинг веб-сервера» писал: «… ООП предоставляет вам множество способов замедлить работу ваших программ …»

Если попытаться классифицировать критические высказывания в адрес ООП, можно выделить несколько аспектов критики данного подхода к программированию.

Критика рекламы ООП.  Критикуется явно высказываемое или подразумеваемое в работах некоторых пропагандистов ООП, а также в рекламных материалах «объектно-ориентированных» средств разработки представление об объектном программировании как о некоем всемогущем подходе, который магическим образом устраняет сложность программирования. Как замечали многие, в том числе упомянутые выше Брукс и Дейкстра, «серебряной пули не существует» — независимо от того, какой парадигмы программирования придерживается разработчик, создание нетривиальной сложной программной системы всегда сопряжено со значительными затратами интеллектуальных ресурсов и времени. Из наиболее квалифицированных специалистов в области ООП никто, как правило, не отрицает справедливость критики этого типа. Оспаривание эффективности разработки методами ООП.  Критики оспаривают тезис о том, что разработка объектно-ориентированных программ требует меньше ресурсов или приводит к созданию более качественного ПО. Проводится сравнение затрат на разработку разными методами, на основании которого делается вывод об отсутствии у ООП преимуществ в данном направлении. Учитывая крайнюю сложность объективного сравнения различных разработок, подобные сопоставления, как минимум, спорны. Производительность объектно-ориентированных программ.  Указывается на то, что целый ряд «врождённых особенностей» ООП-технологии делает построенные на её основе программы технически менее эффективными, по сравнению с аналогичными необъектными программами. Не отрицая действительно имеющихся дополнительных накладных расходов на организацию работы ООП-программ (см. раздел «Производительность» выше), нужно, однако, отметить, что значение снижения производительности часто преувеличивается критиками. В современных условиях, когда технические возможности компьютеров чрезвычайно велики и постоянно растут, для большинства прикладных программ техническая эффективность оказывается менее существенна, чем функциональность, скорость разработки и сопровождаемость. Лишь для некоторого, очень ограниченного класса программ (ПО встроенных систем, драйвера устройств, низкоуровневая часть системного ПО) производительность остаётся критическим фактором. Критика отдельных технологических решений в ООП-языках и библиотеках.  Эта критика многочисленна, но затрагивает она не ООП как таковое, а приемлемость и применимость в конкретных случаях тех или иных реализаций её механизмов. Одним из излюбленных объектов критики является язык C++, входящий в число наиболее распространённых промышленных ООП-языков. Объектно-ориентированные языки

Многие современные языки специально созданы для облегчения объектно-ориентированного программирования. Однако следует отметить, что можно применять техники ООП и для не-объектно-ориентированного языка и наоборот, применение объектно-ориентированного языка вовсе не означает, что код автоматически становится объектно-ориентированным.

Современный объектно-ориентированный язык предлагает, как правило, следующий обязательный набор синтаксических средств:

Объявление классов с полями (данными — членами класса) и методами (функциями — членами класса). Механизм расширения класса (наследования) — порождение нового класса от существующего с автоматическим включением всех особенностей реализации класса-предка в состав класса-потомка. Большинство ООП-языков поддерживают только единичное наследование. Средства защиты внутренней структуры классов от несанкционированного использования извне. Обычно это модификаторы доступа к полям и методам, типа public, private, обычно также protected, иногда некоторые другие. Полиморфные переменные и параметры функций (методов), позволяющие присваивать одной и той же переменной экземпляры различных классов. Полиморфное поведение экземпляров классов за счёт использования виртуальных методов. В некоторых ООП-языках все методы классов являются виртуальными.

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

Конструкторы, деструкторы, финализаторы. Свойства (аксессоры). Индексаторы. Интерфейсы — как альтернатива множественному наследованию. Переопределение операторов для классов.

Часть языков (иногда называемых «чисто объектными») целиком построена вокруг объектных средств — в них любые данные (возможно, за небольшим числом исключений в виде встроенных скалярных типов данных) являются объектами, любой код — методом какого-либо класса и невозможно написать программу, в которой не использовались бы объекты. Примеры подобных языков — Java или Ruby. Другие языки (иногда используется термин «гибридные») включают ООП-подсистему в исходно процедурный язык. В них существует возможность программировать, не обращаясь к объектным средствам. Классические примеры — C++ и Delphi Pascal.

Примечания ^ 1 2 Бадд Т. Объектно-ориентированное программирование в действии / Перев. с англ. — СПб.: Питер, 1997. ISBN 5-88782-270-8 ^ Гради Буч. Объектно-ориентированный анализ и проектирование с примерами приложений на C++. 2-е изд. / Пер. с англ. — М.:"Издательство Бином", СПб:"Невский диалект", 1998 г. ISBN 5-7989-0067-3, ISBN 5-7940-0017-1, стр. 276—278 См. также Литература Иан Грэхем Объектно-ориентированные методы. Принципы и практика = Object-Oriented Methods: Principles & Practice. — 3-е изд. — М.: «Вильямс», 2004. — С. 880. — ISBN 0-201-61913-X Антони Синтес Освой самостоятельно объектно-ориентированное программирование за 21 день = Sams Teach Yourself Object-Oriented Programming in 21 Days. — М.: «Вильямс», 2002. — С. 672. — ISBN 0-672-32109-2 Ссылки Структурное программирование

Структурное программирование — методология разработки программного обеспечения , в основе которой лежит представление программы в виде иерархической структуры блоков . Предложена в 70-х годах XX века Э. Дейкстрой , разработана и дополнена Н. Виртом .

В соответствии с данной методологией

Любая программа представляет собой структуру, построенную из трёх типов базовых конструкций: последовательное исполнение — однократное выполнение операций в том порядке, в котором они записаны в тексте программы; ветвление — однократное выполнение одной из двух или более операций, в зависимости от выполнения некоторого заданного условия; цикл — многократное исполнение одной и той же операции до тех пор, пока выполняется некоторое заданное условие (условие продолжения цикла). В программе базовые конструкции могут быть вложены друг в друга произвольным образом, но никаких других средств управления последовательностью выполнения операций не предусматривается. Повторяющиеся фрагменты программы (либо не повторяющиеся, но представляющие собой логически целостные вычислительные блоки) могут оформляться в виде т. н. подпрограмм ( процедур или функций ). В этом случае в тексте основной программы, вместо помещённого в подпрограмму фрагмента, вставляется инструкция вызова подпрограммы . При выполнении такой инструкции выполняется вызванная подпрограмма, после чего исполнение программы продолжается с инструкции, следующей за командой вызова подпрограммы. Разработка программы ведётся пошагово, методом «сверху вниз».

Сначала пишется текст основной программы, в котором, вместо каждого связного логического фрагмента текста, вставляется вызов подпрограммы, которая будет выполнять этот фрагмент. Вместо настоящих, работающих подпрограмм, в программу вставляются «заглушки», которые ничего не делают. Полученная программа проверяется и отлаживается. После того, как программист убедится, что подпрограммы вызываются в правильной последовательности (то есть общая структура программы верна), подпрограммы-заглушки последовательно заменяются на реально работающие, причём разработка каждой подпрограммы ведётся тем же методом, что и основной программы. Разработка заканчивается тогда, когда не останется ни одной «затычки», которая не была бы удалена. Такая последовательность гарантирует, что на каждом этапе разработки программист одновременно имеет дело с обозримым и понятным ему множеством фрагментов, и может быть уверен, что общая структура всех более высоких уровней программы верна. При сопровождении и внесении изменений в программу выясняется, в какие именно процедуры нужно внести изменения, и они вносятся, не затрагивая части программы, непосредственно не связанные с ними. Это позволяет гарантировать, что при внесении изменений и исправлении ошибок не выйдет из строя какая-то часть программы, находящаяся в данный момент вне зоны внимания программиста.

История

Методология структурного программирования появилась как следствие возрастания сложности решаемых на компьютерах задач, и соответственного усложнения программного обеспечения. В 70-е годы XX века объёмы и сложность программ достигли такого уровня, что «интуитивная» (неструктурированная, или «рефлекторная») разработка программ, которая была нормой в более раннее время, перестала удовлетворять потребностям практики. Программы становились слишком сложными, чтобы их можно было нормально сопровождать, поэтому потребовалась какая-то систематизация процесса разработки и структуры программ.

Наиболее сильной критике со стороны разработчиков структурного подхода к программирования подвергся оператор GOTO (оператор безусловного перехода), имевшийся тогда почти во всех языках программирования. Неправильное и необдуманное использование произвольных переходов в тексте программы приводит к получению запутанных, плохо структурированных программ (т.н. спагетти-кода ), по тексту которых практически невозможно понять порядок исполнения и взаимозависимость фрагментов.

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

Методология структурной разработки программного обеспечения была признана «самой сильной формализацией 70-х годов». После этого слово «структурный» стало модным в отрасли, и его начали использовать везде, где надо и где не надо. Появились работы по «структурному проектированию», «структурному тестированию», «структурному дизайну» и так далее. В общем, произошло примерно то же самое, что происходило в 90-х годах и происходит в настоящее время с терминами «объектный», «объектно-ориентированный» и «электронный».

Перечислим некоторые достоинства структурного программирования:

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

Процедурное (императивное) программирование является отражением архитектуры традиционных ЭВМ, которая была предложена фон Нейманом в 40-х годах. Теоретической моделью процедурного программирования служит алгоритмическая система под названием «машина Тьюринга».

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

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

// if (window.showTocToggle) { var tocShowText = "показать"; var tocHideText = "убрать"; showTocToggle(); } //

Процедурные языки программирования

 

Литература Джозеф Джарратано, Гари Райли Глава 10. Процедурное программирование // «Экспертные системы: принципы разработки и программирование» : Пер. с англ. — М. : 2006. — 779-851 стр., «Вильямс» Декларативное программирование

Декларативное программирование — термин с двумя различными значениями.

Согласно первому определению, программа «декларативна», если она описывает каково нечто, а не как его создать . Например, веб-страницы на HTML декларативны, так как они описывают что должна содержать страница, а не как отображать страницу на экране. Этот подход отличается от языков императивного программирования , требующих от программиста указывать алгоритм для исполнения.

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

 

Обобщённое программирование

Обобщённое программирование — парадигма программирования , заключающаяся в таком описании данных и алгоритмов , которое можно применять к различным типам данных , не меняя само это описание. В том или ином виде поддерживается разными языками программирования . Возможности обобщённого программирования впервые появились в 70-х годах в языках CLU и Ada , а затем во многих объектно-ориентированных языках, таких как C++ , Java , D и языках для платформы .NET .

// if (window.showTocToggle) { var tocShowText = "показать"; var tocHideText = "убрать"; showTocToggle(); } //

Общий механизм

Средства обобщённого программирования реализуются в языках программирования в виде тех или иных синтаксических средств, дающих возможность описывать данные (типы данных) и алгоритмы (процедуры, функции, методы), параметризуемые типами данных. У функции или типа данных явно описываются формальные параметры-типы. Это описание является обобщённым и в исходном виде непосредственно использовано быть не может.

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

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

Способы реализации

Известно два основных способа реализации поддержки обобщённого программирования в компиляторе.

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

Основная статья : Шаблоны C++

В языке C++ обобщённое программирование основывается на понятии «шаблон», обозначаемом ключевым словом template . Широко применяется в стандартной библиотеке C++ (см. STL ), а также в сторонних библиотеках boost , Loki . Большой вклад в появление развитых средств обобщённого программирования в C++ внёс Александр Степанов .

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

// Описание шаблонной функции

template <typename T> T max ( T x, T y )

{ if ( x < y ) return y; else return x; } ...

// Применение шаблонной функции

int a = max<int> ( 10 , 15 ) ; ...

double f = max<double> ( 123.11 , 123 . 12 ) ; ...

 

Java

Java предоставляет средства обобщённого программирования, синтаксически основанные на C++, начиная с версии J2SE 5.0. В этом языке имеются generics или «контейнеры типа T» — подмножество обобщённого программирования.

.NET

На платформе .NET средства обобщённого программирования появились в версии 2.0.

Пример на C#

interface IPerson

{ string GetFirstName ( ) ;

string GetLastName ( ) ; }

class Speaker

{ public void speakTo<T> ( T person ) where T : IPerson

{

string name = person. GetFirstName ( ) ;

this . say ( "Hello, " + name ) ;

}

}

D

В языке «D» средства обобщённого программирования значительно эволюционировали в сравнении с С++, как в процедурной, так и в объектно ориентированной составляющей. Значительно проще стало и лингвистическое представление.

Пример рекурсивной генерации на основе шаблонов D:

// http://digitalmars.com/d/2.0/template.html

template Foo ( T, R... ) // T - тип, R - набор типов

{ void Foo ( T t, R r )

{ writefln ( t ) ;

static if ( r. length ) // if more arguments

Foo ( r ) ; // do the rest of the arguments

}

}

void main ( )

{ Foo ( 1 , 'a' , 6.8 ) ;

}

/+++++++++++++++

prints:

1

a

6.8

+++++++++++++++/

Порождающее программирование

 

Аспектно-ориентированное программирование

Аспектно-ориентированное программирование (АОП) — парадигма программирования , основанная на идее разделения функциональности, особенно сквозной функциональности, для улучшения разбиения программы на модули .

Методология аспектно-ориентированного программирования была предложена группой инженеров исследовательского центра Xerox PARC под руководством Грегора Кикзалеса (Gregor Kiczales). Ими же был разработан первый, и наиболее успешный до сих пор, контекстно-ориентированный язык программирования AspectJ .

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

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

Все языки АОП предоставляют способы для выделения сквозной функциональности в отдельную сущность. Различие между ними заключается в удобстве, безопасности и области применения средств, которые они предоставляют. Наиболее популярный на данный момент язык АОП — AspectJ. Используемые в нем понятия распространились на большинство языков АОП.

Основные понятия AspectJ:

Точка выполнения ( англ. JoinPoint ) — определенная точка выполнения программы. Срез ( англ. PointCut ) — набор точек выполнения программы. Применение ( англ. Advice ) — состоит из условий применения и реализации функциональности. Условия применения определяют до, после или вместо какого среза надо вставить требуемую функциональность. Аспект ( англ. Aspect ) — модуль AspectJ. Представление ( англ. Introduction ) — метод изменения структуры класса путем введения новых полей и методов, а также изменения иерархии наследования. Реализации   Рекурсия

 

Автоматное программирование

 

Событийно-ориентированное программирование

 

Компонентно-ориентированное программирование

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

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

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

Прикладное программирование подчинено проблемной направленности, отражающей компьютеризацию информационных и вычислительных процессов численной обработки, исследованных задолго до появления ЭВМ. Именно здесь быстро проявился явный практический результат. Естественно, в таких областях программирование мало отличается от кодирования, для него, как правило, достаточно операторного стиля представления действий. В практике прикладного программирования принято доверять проверенным шаблонам и библиотекам процедур, избегать рискованных экспериментов. Ценится точность и устойчивость научных расчетов. Язык Фортран - ветеран прикладного программирования. Лишь в последнее десятилетие он стал несколько уступать в этой области Паскалю-Си, а на суперкомпьютерах - языкам параллельного программирования, таким как Sisal. [ [47] , [83] , [84] , [85] ]

Теоретическое программирование придерживается публикационной направленности, нацеленной на сопоставимость результатов научных экспериментов в области программирования и информатики. Программирование пытается выразить свои формальные модели, показать их значимость и фундаментальность. Эти модели унаследовали основные черты родственных математических понятий и утвердились как алгоритмический подход в информатике. [ [38] ] Стремление к доказательности построений и оценка их эффективности, правдоподобия, правильности, корректности и других формализуемых отношений на схемах и текстах программ послужили основой структурного программирования [ [11] , [70] ] и других методик достижения надежности процесса разработки программ, например, грамотное программирование [ [48] ]. Стандартные подмножества Алгола и Паскаля, послужившие рабочим материалом для теории программирования, сменились более удобными для экспериментирования аппликативными языками, такими как ML, Miranda, Scheme и другие диалекты Лиспа. Теперь к ним присоединяются подмножества C и Java.

Функциональное программирование сформировалось как дань математической направленности при исследовании и развитии искусственного интеллекта и освоении новых горизонтов в информатике. Абстрактный подход к представлению информации, лаконичный, универсальный стиль построения функций, ясность обстановки исполнения для разных категорий функций, свобода рекурсивных построений, доверие интуиции математика и исследователя, уклонение от бремени преждевременного решения непринципиальных проблем распределения памяти, отказ от необоснованных ограничений на область действия определений - все это увязано Джоном Мак-Карти в идее языка Лисп [ [75] ]. Продуманность и методическая обоснованность первых реализаций Лиспа позволила быстро накопить опыт решения новых задач, подготовить их для прикладного и теоретического программирования. В настоящее время существуют сотни функциональных языков программирования, ориентированных на разные классы задач и виды технических средств. [ [8] , [23] , [78] , [79] , [80] , [81] , [82] ]

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

Низкоуровневое программирование характеризуется аппаратным подходом к организации работы компьютера, нацеленным на доступ к любым возможностям оборудования. В центре внимания - конфигурация оборудования, состояние памяти, команды, передачи управления, очередность событий, исключения и неожиданности, время реакции устройств и успешность реагирования. Ассемблер в качестве предпочтительного изобразительного средства на некоторое время уступил языкам Паскаль и Си даже в области микропрограммирования, но усовершенствование пользовательского интерфейса может восстановить его позиции. [ [20] , [28] , [71] , [85] ]

Системное программирование долгое время развивалось под прессом сервисных и заказных работ. Свойственный таким работам производственный подход опирается на предпочтение воспроизводимых процессов и стабильных программ, разрабатываемых для многократного использования. Для таких программ оправдана компиляционная схема обработки, статический анализ свойств, автоматизированная оптимизация и контроль. В этой области доминирует императивно-процедурный стиль программирования, являющийся непосредственным обобщением операторного стиля прикладного программирования. Он допускает некоторую стандартизацию и модульное программирование, но обрастает довольно сложными построениями, спецификациями, методами тестирования, средствами интеграции программ и т.п. Жесткость требований к эффективности и надежности удовлетворяется разработкой профессионального инструментария, использующего сложные ассоциативно семантические эвристики наряду с методами синтаксически-управляемого конструирования и генерации программ. Бесспорный потенциал такого инструментария на практике ограничен трудоемкостью освоения - возникает квалификационный ценз [ [58] ].

Высокопроизводительное программирование нацелено на достижение предельно возможных характеристик при решении особо важных задач. Естественный резерв производительности компьютеров - параллельные процессы. Их организация требует детального учета временных отношений и неимперативного стиля управления действиями. Суперкомпьютеры, поддерживающие высокопроизводительные вычисления, потребовали особой техники системного программирования. Графово-сетевой подход к представлению систем и процессов для параллельных архитектур получил выражение в специализированных языках параллельного программирования и суперкомпиляторах, приспособленных для отображения абстрактной иерархии процессов уровня задач на конкретную пространственную структуру процессоров реального оборудования [ [10] , [15] , [84] ].

 

 

Таблица 15.1. Материалы Интернет-Университета, дополняющие лекции курса. № Тема лекции Авторы, название курса и URL 1 Многоликое программирование

Баженова И.Ю., Сухомлин В.А. Введение в программирование http://www.intuit.ru/department/pl/plintro/

Непейвода Н.Н. Стили и методы программирования http://www.intuit.ru/department/se/progstyles/

2 Определение языков программирования Вояковская Н.Н., Москаль А.Е., Булычев Д.Ю., Терехов А.А. Разработка компиляторов http://www.intuit.ru/department/sa/compilersdev/ 3 Ассемблер Новиков Ю.В., Скоробогатов П.К. Основы микропроцессорной техники http://www.intuit.ru/department/hardware/mpbasics/6.2.1 . Ассемблер MPASM 4 Машинно ориентированное программирование

Галатенко В.А. Программирование в стандарте POSIX http://www.intuit.ru/department/se/pposix/

Язык программирования C http://www.intuit.ru/department/pl/cpl/

Якушева Н.М. Visual Basic http://www.intuit.ru/department/pl/vb/

5 Макрообработка текстов Храмцов П.,Б., Брик С.А., Русак А.М., Сурин А.И. Введение в HTML http://www.intuit.ru/department/internet/htmlintro/ 6 Языки управления процесс

Курячий Г.В., Маслинский К.А. Операционная система Linux http://www.intuit.ru/department/os/linux/

Костромин В.А.Основы работы в ОС Linux http://www.intuit.ru/department/os/baselinuxwork/ Оболочка bash

7 Функциональное программирование Городняя Л.В. Основы функционального программирования http://www.intuit.ru/department/pl/funcpl/ 8 Стандартное системное программирование

Страуструп Бьерн Язык программирования C++ для профессионалов http://www.intuit.ru/department/pl/cpp2/

Биллиг В.А. Основы программирования на C# http://www.intuit.ru/department/pl/csharp/

Андреева Т.А. http://www.intuit.ru/department/pl/plpascal/ Программирование на языке Pascal

9 Декларативное программирование

Алексеев В.Е., Таланов В.А. Структуры данных и модели вычислений http://www.intuit.ru/department/algorithms/dscm/

Шрайнер П.А. Основы программирования на языке Пролог http://www.intuit.ru/department/pl/plprolog/

10 Объектно-ориентированное программирование

Мейер Бертран http://www.intuit.ru/department/se/oopbases/

Основы объектно-ориентированного программирования http://www.intuit.ru/department/se/ooad/ Основы объектно-ориентированного проектирования

11 Языки параллельного программирования. Барский А.Б. Параллельное программирование http://www.intuit.ru/department/se/parallprog/ 12 Функции высших порядков

Сузи Р.А. http://www.intuit.ru/department/pl/python/ Язык программирования Python

Верещагин Н.К., Шень А.Х. Языки и исчисления http://www.intuit.ru/department/calculate/lancalc/

13 Оптимизация программ. Чеповский А.М., Макаров А.В. Скоробогатов С.Ю. Common Intermediate Language и системное программирование в Microsoft .NET http://www.intuit.ru/department/pl/cil/ 14 Разработка программ

Терехов А.Н. Введение в технологию программирования http://www.intuit.ru/department/se/introprogteach/

Леоненков А.В. Нотация и семантика языка UML http://www.intuit.ru/department/pl/umlbasics/

Котляров В.П. http://www.intuit.ru/department/se/testing/ Основы тестирования программного обеспечения

15 Перспективы парадигм программирования Кулямин В.В. Компонентный подход в программировании http://www.intuit.ru/department/se/compprog/ on_load_lecture() 5.2. Теоретическое программирование

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

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

алгебраическое, инсерционное программирование (Летичевский А.А. и др.) [ 5.24 , 5.25 ]; экспликативное программирование (Редько В.Н.) и наука о программах (программология), объединяющая логический и математический аппарат для конструирования программ [ 5.26 - 5.28 ]; алгебро-алгоритмическое программирование (Цейтлин Г.Е.), объединяющее алгебраический аппарат, теорию алгоритмов и операции над множествами [ 5.29 - 5.30 ]. Остановимся на их краткой характеристике. 5.2.1. Алгебраическое, инсерционное программирование

Алгебраическое программирование (АП) - это конструирование программ с алгебраическими преобразованиями и функциями интеллектуальных агентов. В основе математического аппарата АП лежит алгебра языка действий АL (Action Language) и понятие транзитивной системы [ 5.24 , 5.25 ], в качестве механизма определения поведения систем и механизмов ее эквивалентности. В качестве понятий в общем случае могут быть компоненты, программы и их спецификации, объекты, взаимодействующие друг с другом и со средой их существования.

Основу АП составляет математическая модель, которая включает в себя следующие понятия:

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

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

Расширение понятия транзитивных систем - это множество заключительных состояний с успешным завершением функционирования системы и без неопределенных состояний. Главный инвариант состояния транзитивной системы - поведение системы, задаваемое выражениями алгебры поведения F(A) на множестве операций алгебры действий - префиксинг a \cdot u, недетерминированный выбор u + v одного из двух поведений u и v со свойством ассоциативности и коммутативности. Конечное поведение системы задается константами \Delta, \perp, 0, которые обозначают соответственно состояние успешного завершения, неопределенного и тупикового. Алгебра поведения включает отношение \le, элемент \perp как наименьший и операции поведения, являющиеся монотонными. Средствами алгебры поведения F(A) доказана теорема про наименьшую неподвижную точку.

Транзитивные системы называют бисимуляцийно эквивалентными, если каждое состояние эквивалентно состоянию другой системы. На множестве поведений определяются новые операции, которые используются для построения программ агентов. К ним относятся следующие операции: последовательная композиция ( u; v) и параллельная композиция u \| v.

Среда Е , где находится объект, определяется как агент в алгебре действий АL и функции погружения от двух аргументов Ins(e, u) = e[u]. Первый аргумент - это поведение среды, второй - поведение агента, который погружается в эту среду с заданным состоянием. Значения функций погружения - это новое состояние одной и той же среды. Базовым понятием является "действие", которое трансформирует состояние агентов, поведение которых, в конце концов, изменяется.

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

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

Язык действий АL имеет синтаксис и семантику. Синтаксис языка задает правила описания действий, семантика - функции, которые определяются средствами и выражениями языка и ставят в соответствие заданным выражениям значения в некоторой семантической области. Разные семантические функции могут давать равные абстракции и свойства программ. Семантика может быть вычислительной и интерактивной. Каждая алгебра действий - это гомоморфный образ алгебры примитивных действий, когда все слагаемые разные, а их представление однозначно с точностью до ассоциативности и коммутативности при детерминированном выборе.

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

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

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

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

Алгебраическое программирование концентрирует внимание на проблемах интеллектуализации и аспектах поведения агентов в распределенной среде, куда они погружаются. Оно постепенно перешло в инсерционное программирование путем вставки, погружения агентов в разнообразные среды для преобразования поведения агентов на основе модели поведения, соответствующей размеченной транзитивной системы и бисимуляционной эквивалентности [ 5.25 ]. Данное программирование обобщает алгебраическое преобразование множества состояний информационной среды на объекты, обладающие поведением. Схема создания агентных программ представлена на рис. 5.12 .

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

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

 Технологическая схема в АП
Рис. 5.12. Технологическая схема в АП

На следующем уровне описания программы находятся функции, которые преобразуют любое агентное выражение в AL-программу действий. Например, функция U развертывания функциональных выражений может быть представлена в виде простых агентных выражений:

 U(u + v) = U(u) + U(v), U (u) = u,

где u, v - параметры агентных выражений.

Функция развертывания агентных функциональных выражений задает семантику AL-программы в виде программы функционирования агента в любой среде. Если функция развертывания обрабатывается за конечное число шагов, то она содержит конечное выражение. Множество всех переходов, заключительных и тупиковых состояний - перечислимое, даже если функция развертывания имеет бесконечное множество нетривиальных итераций. Состоянием транзитивной системы являются ограниченные выражения, определяемые операцией выбора +, соотношением x + 0 = a и отношением перехода с правилом u \to U(u).

Третий уровень программы - это функция развертывания погружений, задаваемых в алгебре действий. Эти функции определяются на объектах среды и агентов, т.е. агентных выражениях. Для корректности функций погружения необходимо, чтобы она зависела только от поведения агента и среды и была непрерывной функцией. Следующим шагом разработки программ является ее реализация в ЯП, например в С++, когда уточняются типы данных и параметры. Затем проводится верификация полученной программы для проверки правильности выполнения ее поведения в заданной модели. Более подробно об инсерционном программировании и средствах его автоматизации в [ 5.25 ].

on_load_lecture()

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

В узком смысле слова, программирование рассматривается как кодирование — реализация одного или нескольких взаимосвязанных алгоритмов на некотором языке программирования. Под программированием также может пониматься разработка логической схемы для ПЛИС , а также процесс записи информации в ПЗУ . В более широком смысле программирование — процесс создания программ, то есть разработка программного обеспечения .

Программирование включает в себя:

// if (window.showTocToggle) { var tocShowText = "показать"; var tocHideText = "убрать"; showTocToggle(); } //

Большая часть работы программиста связана с написанием исходного кода на одном из языков программирования .

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

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

В некоторых языках вместо машинного кода генерируется интерпретируемый двоичный код «виртуальной машины», также называемый байт-кодом ( byte-code ). Такой подход применяется в Forth , некоторых реализациях Lisp [1] , Java , Perl , Python , а также в языках платформы Microsoft .NET .

См. также

 

Алгоритм по информатике примеры и блок схемы Алгоритм по информатике примеры и блок схемы Алгоритм по информатике примеры и блок схемы Алгоритм по информатике примеры и блок схемы Алгоритм по информатике примеры и блок схемы Алгоритм по информатике примеры и блок схемы Алгоритм по информатике примеры и блок схемы Алгоритм по информатике примеры и блок схемы Алгоритм по информатике примеры и блок схемы Алгоритм по информатике примеры и блок схемы Алгоритм по информатике примеры и блок схемы Алгоритм по информатике примеры и блок схемы Алгоритм по информатике примеры и блок схемы Алгоритм по информатике примеры и блок схемы Алгоритм по информатике примеры и блок схемы

Тоже читают:



Летние платья и туники крючком схемы и описание

Схема металлоискателя большой глубине

Замена тормозных колодок на опель зафира б своими руками

Как сделать в инстаграме место где была сделана фотография

Домовёнок на новоселье своими руками