Парадигмы программирования.
Парадигма программирования — это совокупность идей и понятий, определяющая стиль написания программ.
Императи́вная парадигма описывает процесс вычисления в виде инструкций, изменяющих состояние программы. Императивная программа очень похожа на приказы, выражаемые повелительным наклонением в естественных языках, то есть это последовательность команд, которые должен выполнить компьютер. Основана на модели конечного автомата Тьюринга-Поста.
Первыми императивными языками были машинные коды — родной язык программирования для компьютера. В этих языках инструкции были крайне просты, что снижало нагрузку на компьютеры, однако затрудняло написание крупных программ. В 1954 появился первый «человеческий» язык программирования — FORTRAN, затем ALGOL, COBOL, BASIC, Pascal, C.
Одна из характерных черт императивного программирования - наличие переменных с операцией "разрушающего присвоения". То есть, была переменная А, было у нее значение Х. Алгоритм предписывает на очередном шаге присвоить переменной А значение Y. То значение, которое было у А, будет "навсегда забыто".
Императивное программирование наиболее пригодно для реализации небольших подзадач, где очень важна скорость исполнения на современных компьютерах. Кроме этого, работа с внешними устройствами, как правило, описывается в терминах последовательного исполнения операций ("открыть кран, набрать воды"), что делает такие задачи идеальными кандидатами на императивную реализацию.
Выбор рамок императивной парадигмы для обучения основам программирования, по-видимому, ни у кого не вызывает сомнения. Этому есть несколько причин:
императивная парадигма наиболее близка природе человека и интуитивному понятию алгоритма на ранних стадиях развития мышления (есть положительный опыт развивающего обучения с элементами алгоритмизации уже в начальной школе);
программирование в рамках императивной парадигмы эффективно для широкого класса задач, многие из которых попадают в зону ближайшего развития учащихся в старших классах базовой школы;
императивная парадигма наиболее близка природе компьютера, основным принципам его функционирования, так как, не смотря на всю сложность современного компьютера, на уровне аппаратной части его можно по-прежнему рассматривать как некоторый автомат (процессор+память+…) с конечным множеством состояний (содержимого памяти);
доля программных продуктов, созданных исключительно в рамках декларативной парадигмы программирования мала; как правило, при решении задач используется сочетание парадигм, одной из которых является императивная;
большой выбор систем программирования в виде самостоятельных программных средств и в виде интегрированных в другие системы подсистем, позволяющих разрабатывать программные продукты с использованием императивной парадигмы;
обширная номенклатура учебных, справочных и прочих публикаций по соответствующим системам программирования в бумажном и электронном видах на различных носителях и в глобальной сети.
Недостаток: в чистом виде позволяет решать только очень простые задачи.
Событийно-управляемое программирование - программирование, при котором задаются реакции программы на различные события (действия пользователя). СУП можно рассматривать как «потомок» императивной парадигмы. СУП имеет 2 подкласса:
1.Параллельное программирование представляет программу в виде набора сообщающихся процессов, которые могут выполняться параллельно. Такие программы могут выполняться как на одном процессоре (чередуя выполнение шагов каждого процесса), так и на нескольких.
В системе параллельных процессов каждый отдельный процесс обрабатывает события. События могут быть как общими для всей системы, так и индивидуальными для одного или нескольких процессов. В таких терминах достаточно удобно описывать, например, элементы графического интерфейса пользователя, или моделирование каких-либо реальных процессов (например, управление уличным движением) - так как понятие события является для таких задач естественным.
2.Объектно-ориентированное программирование - технология программирования, при которой программа рассматривается как набор объектов и их взаимодействий. Каждый объект программы является экземпляром некоторого класса; — классы могут наследовать атрибуты и методы их родительских классов, в то же время добавляя свои собственные. Иерархия классов позволяет моделировать сущности решаемой задачи на нескольких уровнях детализации и в дальнейшем использовать класс, отвечающий уровню детализации, необходимому для решения конкретной подзадачи.
Важно выделить следующие основные свойства объектов:
1.) Так как один объект может воздействовать на другой исключительно при помощи посылки последнему сообщений, он не может как-либо непосредственно работать с собственными данными "собеседника", и, следовательно, не может нарушить их внутреннюю согласованность. Это свойство (сокрытие данных) принято называть инкапсуляцией.
2.) Так как объекты взаимодействуют исключительно за счет обмена сообщениями, объекты-собеседники могут ничего не знать о реализации обработчиков сообщений у своего визави. Взаимодействие происходит исключительно в терминах сообщений/событий, которые достаточно легко привязать к предметной области. Это свойство (описание взаимодействия исключительно в терминах предметной области) называют абстракцией.
3.) Объекты взаимодействуют исключительно через посылку сообщений друг другу. Поэтому если в каком-либо сценарии взаимодействия объектов заменить произвольный объект другим, способным обрабатывать те же сообщения, сценарий так же будет реализуем. Это свойство (возможность подмены объекта другим объектом со сходной структурой класса) называется полиморфизмом.
Многие современные языки поддерживают ООП, хотя и в разной степени: — чисто объектно-ориентированные языки, например, Smalltalk и Ruby, разработаны для того, чтобы поддерживать и даже навязывать объектно-ориентированный стиль разработки, и не поддерживают другие стили программирования; — преимущественно объектно-ориентированные языки, например, Java, C++ и Python, разработаны в основном для поддержки ООП, но позволяют использовать элементы процедурного программирования; — исторически процедурные языки, например, Perl и Fortran 2002, были доработаны и в них была добавлена поддержка некоторых элементов ООП.
Декларативная парадигма программирования определяет процесс вычислений посредством описания логики самого вычисления, а не управляющей логики программы.
Декларативное программирование является противоположностью императивного программирования; первое описывает, что необходимо сделать, а второе — как именно это сделать.
Наиболее важными разновидностями декларативного программирования, являются функциональное и логическое (или реляционное) программирование.
1.Функциональное программирование представляет собой одну из альтернатив императивному подходу. Оно основано на лямбда-исчислении Черча. В императивном программировании алгоритмы - это описания последовательно исполняемых операций. Здесь существует понятие "текущего шага исполнения" (то есть, времени), и "текущего состояния", которое меняется с течением этого времени.
В функциональном программировании понятие времени отсутствует. Программы являются выражениями, исполнение программ заключается в вычислении этих выражений.
Так как порядок вычисления подвыражений не имеет значения, функциональное программирование может быть естественным образом реализовано на платформах, поддерживающих параллелизм.
Функциональное программирование, как и другие модели "неимперативного" программирования, обычно применяется для решения задач, которые трудно сформулировать в терминах последовательных операций. Практически все задачи, связанные с искусственным интеллектом, попадают в эту категорию. Среди них следует отметить задачи распознавания образов, общение с пользователем на естественном языке, реализацию экспертных систем, автоматизированное доказательство теорем, символьные вычисления. Эти задачи далеки от традиционного прикладного программирования, поэтому им уделяется не так много внимания в учебных программах по информатике.
Логическое программирование
В функциональном программировании программы - это выражения, и их исполнение заключается в вычислении их значения. В логическом программировании программа представляет из себя некоторую теорию (описанную на достаточно ограниченном языке), и утверждение, которое нужно доказать. В доказательстве этого утверждения и будет заключаться исполнение программы.
Логическое программирование и язык Пролог появились в результате исследования в области анализа естественных языков. Впоследствии было обнаружено, что логическое программирование столь же эффективно в реализации других задач искусственного интеллекта.
Логическое программирование допускает естественную параллельную реализацию.
Язык Си.
Язык С и его последующие реализации C++ и C# занимают особое место среди языков программирования. Наряду со средствами языков высокого уровня, реализующими концепцию нисходящего структурного проектирования, язык содержит средства для программирования на низком, системном уровне (адрес значения, значение по указанному адресу, побитовые операции, операции сдвига). Благодаря гибкости и компактности своих конструкций С завоевал наибольшую популярность в среде профессиональных программистов и широко используется при разработке системных и прикладных программ.
Средства языков семейства С, декларируемые как преимущества, при некорректном использовании становятся недостатками. Гибкость языка достигается в основном за счет снижения контроля над правильностью использования данных различных типов и предоставления программисту возможности непосредственного изменения содержимого ячеек памяти. Отказ от проверки соответствия типов данных предоставляет программисту наибольшую свободу. Однако, ответственность за корректность программ при этом полностью ложится на программиста. Средства непосредственной работы с содержимым ячеек памяти позволяют эффективно использовать возможности конкретного компьютера. Однако, такие мощные средства требуют от программиста осторожности, аккуратности и хорошего знания языка и особенностей распределения памяти конкретного компьютера.
Любая программа на языке С состоит из одной и более функций, одна из которых должна иметь имя main, и именно ей передается управление из операционной системы. Функция - это коллективное имя для некоторой группы описаний и операторов, заключенных в фигурные скобки { } и являющихся телом функции.
Программа на языке С может иметь следующую структуру:
программа С <раздел препроцессора>
<раздел функций>
<раздел препроцессора> #<опция препроцессора>
{#<опция препроцессора>}
В системах программирования семейства С используется двухпроходный транслятор типа компилятор. На первом проходе обрабатываются директивы препроцессора, на втором формируется вариант программы в машинных кодах.
Так, например, в результате обработки директивы #include <<имя файла>> (внешние угловые скобки – символы языка С) в текст программы вместо этой директивы будет помещен файл описания стандартных функций системы (файл библиотеки). Файлы библиотеки языка С имеют расширение h (например, stdio.h – файл описания функций ввода/вывода).
Директива #define <идентификатор> <конструкция С> задает так называемое макроопределение. В результате обработки этой директивы каждое вхождение <идентификатор> в текст программы после макроопределения заменяется на <конструкция С>. Например, #define n 100 – макроопределение целочисленной константы n.
Внимание: в языке С строчные и прописные буквы во всех конструкциях различаются. Так N и n – различные идентификаторы.
раздел функций> <описание функции>
{<описание функции>}
<описание функции> [<идентификатор типа>] <идентификатор>([< формальные параметры>])
<составной оператор>
Здесь <идентификатор типа> – тип возвращаемого значения (тип значения <идентификатор>).
<формальные параметры>
<идентификатор типа> <идентификатор>{, <идентификатор типа> <идентификатор>}
Идентификатор типа |
Размер, бит |
Диапазон |
unsigned char |
8 |
0÷255 |
Char |
8 |
-128÷127 |
Enum |
16 |
-32768÷32767 |
unsigned short |
16 |
0÷65535 |
Short |
16 |
-32768÷32767 |
unsigned int |
16 |
0÷65535 |
Int |
16 |
-32768÷32767 |
unsigned long |
32 |
0÷4294967295 |
Long |
32 |
-2147483648÷2147483647 |
Float |
32 |
3.4∙10-38÷3.4 10+38 |
Double |
64 |
1.7∙10-308÷1.7 10+308 |
long double |
80 |
1.7 10-4932÷1.7 10+4932 |
<идентификатор типа> задается стандартными служебными словами int (целый), long (длинный), short (короткий), unsigned (беззнаковый), char (символьный), float (действительный одинарной точности), double (действительный двойной точности), pointer (указатель), enum (перечислимый). Допустимые в языке типы данных, размеры выделяемой памяти в битах и диапазон представления приведены в табл. 1.1.
<составной оператор> {
<оператор>|<выражение>;
{<оператор>|<выражение>;}
}
Здесь первая открывающаяся { и последняя закрывающаяся } являются символами алфавита языка С. Значение функции определяется значением <выражения> необязательного оператора return <выражение>, вложенного в <составной оператор>. Если значение функции не определяется (оператор return <выражение> отсутствует), то рекомендуем в качестве типа значения использовать void (отсутствие типа)
В <составном операторе> могут содержаться операторы резервирования памяти, с помощью которых описываются локальные переменные
<резервирование памяти> <идентификатор типа> <идентификатор>{, <идентификатор>}
Например, оператор int i, j; описывает переменные i и j как целые, а оператор float t; определяет переменную t как действительную одинарной точности.
Операции в Си.
Операции выполняются в порядке увеличения их приоритета. Основные операции перечислены в таблице.
|
Приоритет |
Операция |
Название |
Порядок выполнения |
1 |
() |
Скобки, вызов функции |
Слева направо |
|
[] |
Индекс элемента массива |
Слева направо |
||
. |
Выделение элемента структуры или Объединения |
Слева направо |
||
-> |
Выделение элемента структуры (объединения), адресуемой (го) указателем |
Слева направо |
||
2 |
! |
Логическое отрицание |
Справа налево |
|
- |
Изменение знака |
Справа налево |
||
++ |
Увеличение на единицу |
Справа налево |
||
-- |
Уменьшение на единицу |
Справа налево |
||
& |
Адрес значения переменной |
Справа налево |
||
* |
Значение по указанному адресу |
Справа налево |
||
3 |
* |
Умножение |
Слева направо |
|
/ |
Деление |
Слева направо |
||
% |
Остаток от деления |
Слева направо |
||
4 |
+ |
Сложение |
Слева направо |
|
- |
Вычитание |
Слева направо |
||
5 |
< |
Меньше |
Слева направо |
|
<= |
Меньше или равно |
Слева направо |
||
> |
Больше |
Слева направо |
||
>= |
Больше или равно |
Слева направо |
||
6 |
== |
Равно |
Слева направо |
|
!= |
Не равно |
Слева направо |
||
7 |
&& |
Логическое И |
Слева направо |
|
8 |
|| |
Логическое ИЛИ |
Слева направо |
|
9 |
?: |
Условная операция |
Слева направо |
|
14 |
= |
Присваивание |
Справа налево |
|
*= |
Умножение и присваивание |
Справа налево |
||
/= |
Деление и присваивание |
Справа налево |
||
%= |
Остаток и присваивание |
Справа налево |
||
+= |
Сложение и присваивание |
Справа налево |
||
-= |
Вычитание и присваивание |
Справа налево |
Следует подчеркнуть особенность операции деления. Эта операция дает целый результат, если оба операнда целые. Например, выражение 9/5 даст результат, равный единице. Чтобы получить действительный результат, необходимо иметь хотя бы один действительный операнд. Так, 9./5 будет равно 1.8.
Операции увеличения на единицу (инкремента) «++» и операция уменьшения на единицу (декремента) «--» имеют префиксную (++n или --n) и постфиксную (n++ или n--) формы записи. В первом случае значение операнда n сначала изменяется, а затем используется для дальнейших вычислений, во втором же случае n сначала используется, а затем изменяется. Так, например, выражение а + b++ означает «сложить а и b и увеличить значение b на единицу», а а + ++b – «увеличить значение b на единицу и сложить а и b».
Операция & применяется к идентификатору переменной, а операция * к выражению, имеющему значение адреса (указателя), так, например, a и *&a эквивалентны.
Логический тип данных в Си.
В языке Си специального логического типа нет, вместо него используются переменные целого типа. Значению "истина" соответствует любое ненулевое целое число, значению "ложь" - ноль. Например, в Си допустим такой фрагмент программы:
int b;
float s;
. . .
if (b != 0)
{
s = 1;
}
Здесь целочисленная переменная b используется в качестве условного выражения в операторе if ("если"). Если значение b отлично от нуля, то выполняется тело оператора if, т.е. переменной s присваивается значение 1; если значение b равно нулю, то тело оператора if не выполняется.
В языке С используется обычный набор операций отношений: < (меньше), <= (меньше или равно), > (больше), >= (больше или равно), == (равно) и != (не равно).
К логическим операциям относятся логическое И или логическое умножение (&&), логическое ИЛИ или логическое сложение (две вертикальные палки), исключающее ИЛИ (^) и логическое отрицание (!).
Если операнд операции отрицания равен нулю, то результат операции будет равен единице; если же значение операнда отлично от нуля, результат операции будет равен нулю.
Операция логического умножения дает значение «истинно», если оба операнда истинны, т.е. отличны от нуля; в противном случае результат операции будет равен нулю, т.е. ложен.
Логическое сложение вырабатывает значение истинно, если хотя бы один из операндов истинен и ложно в противном случае.
Операция исключающего ИЛИ дает истинное значение, если операнды имеют противоположные значения (истинно и ложно), и ложное значение, когда оба операнда одновременно ложны или истинны.
По сравнению с операциями отношения они имеют меньший приоритет. В качестве операндов могут использоваться данные любого типа.
Условный оператор.
Условный оператор позволяет выбрать и выполнить один из двух входящих в него операторов в зависимости от значения некоторого выражения.
Синтаксис условного оператора:
If (<выражение>)
<опер 1 или выраж 1>;
[else
<опер 2 или выраж 2>;]
<выражение> является любым выражением, которое приводится или может быть приведено к целочисленному значению. Если <выражение> принимает значение «истинна», т.е. отлично от нуля, то выполняется <опер 1 или выраж 1>, если же оно принимает значение «ложь», т.е. равно нулю, то выполняется <опер 2 или выраж 2>
Пример, оператор вычисляющий модуль y=|x|:
if (x<0)
y = -x;
else
y = x;
Запись без блока else называется сокращенным условным оператором. В нем, если проверяемое выражение принимает значение «ложь», то выполняется выражение следующее сразу за условным оператором.
В отличие от уже известного нам языка Паскаль, язык Си имеет следующие особенности:
Begin и end в Паскале заменяется {} в си;
Равно = в паскале и равно == в си (но не операция присваивания);
Не равно <> в Паскале и не равно != в си;
Операции and и or в паскале заменяются && и || соответствующими операциями в Си.
Тернарный условный оператор в Си.
Операция условия.
Операция условия «?:» применяется для записи условного оператора выражения:
(<выражение 1>) ? <выражение 2> : <выражение 3>
Если <выражение 1> истинно (отлично от нуля), то значением всего условного выражения будет <выражение 2>. Если же <выражение 1> ложно (равно нулю), то за значение условного выражения принимается величина, вычисляемая в <выражение 3> .
Пример, оператор вычисляющий модуль y=|x|:
y = (x<0) ? –x : x;
Оператор выбора.
Если в программе необходимо выбрать один из нескольких многочисленных вариантов, то вместо вложенной конструкции if более целесообразно применять оператор-переключатель switch, имеющий следующий синтаксис:
<переключатель>=switch (<выражение>)
{
{|case<const>:
{| (|<оператор>|<выраж>|);
Break;|}
[|default:
{|(|<оператор>|<выраж>|);|}
|]
}
Здесь для выполнения выбирается тот оператор или выражение, константа которого совпадает со значением выражения. Как выражение, так и константы должны иметь значения целого или символьного типа.
Вариант default не обязательно должен быть последним и вообще может отсутствовать. Во втором случае, если значении выражение не соответствует ни одной из констант, то управление передается оператору, следующему за оператором switch.
Пример на случай записи одного оператора для нескольких констант (альтернатива записи через запятую для ТП):
switch n
case 3:;
case 4:;
case 5:
{
….
break;
}
Циклы.
Вычислительные процессы с многократным повторением однотипных вычислений/действий для различных значений входящих величин/данных называются циклическими, повторяющиеся участки вычислений – циклами, изменяющиеся в цикле величины – переменными цикла. В зависимости от того, где происходит проверка условия продолжения или окончания цикла, циклы разделяют на: цикл с предусловием и цикл с постусловием.
Цикл с пред условием:
<цикл с пред условием>=while(<выражение>)
(|<оператор>|<выражение>|)
Если значение выражения равно 0, то цикл свою работу завершает.
Цикл с пост условием:
<цикл с пост условием>=do
(|<оператор>|<выражение>|);
While (<выражение>);
Отличия от ТР состоят в следующем:
ТР ТС
Как избежать зацикливания?
Тело цикла должно содержать хотя бы один оператор, влияющий на условие окончания или продолжения, иначе цикл будет продолжаться бесконечно. Условие окончание цикла должно быть в конце концов удовлетворено
Цикл с параметром:
<цикл for>=for(<выр1>;<выр2>;<выр3>)
(|<опер>|<выр>|)
<выр1> служит для инициализации параметра цикла и вычисляется только 1 раз перед началом выполнения цикла. <выр2> определяет условие продолжение цикла. Оно выполняется перед каждым шагом цикла. Когда выражение становится ложным, цикл завершается. Если же оно истинно, то выполняется оператор тела цикла, который может быть простым или составным. <выр3> вычисляется в конце каждого выполнения тела цикла. Обычно оно применяется для коррекции значения параметра цикла.
В операторе for можно опустить одно, два или даже все выражения, однако «;» должны оставаться на месте. Это будет равносильно тому, что значение этих выражений будет всегда истинна и цикл никогда не завершится, если в теле цикла нет прерывания.
Семантика данного оператора реализуется через цикл с предусловием и представляется следующим образом:
нет
Операция запятая.
Операция «запятая» увеличивает гибкость использования оператора цикла for, позволяя включать в его спецификацию несколько инициализирующих или корректирующих выражений. Операция «запятая» объединяет два выражения в одно и гарантирует их вычисление слева на право. Например:
for (a=1,b=5; a<=6; a++,b--)
…
В этом примере первое и последнее выражения состоят из двух выражений. Эти выражения могут быть сколь угодно сложными. Применение операции «запятая» возможно и во втором логическом выражении, но за значение всего выражения будет принято только значение последнего из объединяемых выражений.
Как избежать зацикливания?
Тело цикла должно содержать хотя бы один оператор, влияющий на условие окончания или продолжения, иначе цикл будет продолжаться бесконечно. Условие окончание цикла должно быть в конце концов удовлетворено.
Потоки данных в Си.
Поток данных в программировании — абстракция, представляет собой источник ввода и/или вывода данных, обычно байтов, связанный с файлом, устройством, либо другим процессом. Потоковый ввод/вывод в C++ осуществляется с помощью потоков библиотеки C++, доступных при подключении заголовочного файла iostream.h . Поток представляет собой объект какого-либо потокового класса.
Извлечением называют операцию «>>»:
<файл-источник> >> <идентификатор>
Для ввода информации с клавиатуры (файла-источника) используется объект cin. Формат записи cin имеет следующий вид:
cin [>> <идентификатор>];
Объект cin имеет некоторые недостатки. Необходимо, чтобы данные вводились в соответствии с форматом переменных, что не всегда может быть гарантировано.
Вставкой называют операцию «<<»:
<файл-приемник> << <выражение>
Эта операция обладает свойством ассоциативности, т.е. возможно объединение в цепочку операций вставки.
Вывод информации на стандартное устройство вывода – экран (файл-приемник). Формат записи cout имеет следующий вид:
сout << <выр> [ << <выр>];
выр - это выражение, которое может быть представлено переменной, константой, выражением или комбинации всех трех типов. Простейший пример применения cout - это вывод, например, символьной строки. Надо помнить, что cout не выполняет автоматический переход на новую строку после вывода информации. Для перевода курсора на новую строку надо вставлять символ ’\n’ или манипулятор endl.
cout << ”строка \n”;
Манипуляторы. Для управления выводом информации используются манипуляторы. Манипуляторы изменяют значение некоторых переменных в объекте cout. Эти переменные называются флагами состояния. Когда объект посылает данные на экран, он проверяет эти флаги. Например манипулятор endl:
cout << ”строка”<<endl;
Манипулятор представляет собой адрес функции. Манипуляторы можно выбрать из набора стандартных(endl), либо создавать самому.