В информатике императивное программирование — это парадигма программирования программного обеспечения , в которой используются операторы , изменяющие состояние программы . Во многом так же, как повелительное наклонение в естественных языках выражает команды, повелительное наклонение программы состоит из команд , которые должен выполнить компьютер . Императивное программирование фокусируется на описании того, как программа работает шаг за шагом, [1] , а не на высокоуровневом описании ее ожидаемых результатов.
Этот термин часто используется в отличие от декларативного программирования , которое фокусируется на том, что должна выполнить программа, без указания всех деталей того, как программа должна достичь результата. [2]
Процедурное программирование — это тип императивного программирования, при котором программа состоит из одной или нескольких процедур (также называемых подпрограммами или функциями). Эти термины часто используются как синонимы, но использование процедур оказывает существенное влияние на то, как выглядят императивные программы и как они создаются. Тяжелое процедурное программирование, в котором изменения состояния локализуются в процедурах или ограничиваются явными аргументами и возвратами процедур, является формой структурного программирования . С 1960-х годов структурное программирование и модульное программирование в целом пропагандировались как методы улучшения удобства сопровождения и общего качества императивных программ. Концепции объектно-ориентированного программирования пытаются расширить этот подход.
Процедурное программирование можно считать шагом к декларативному программированию. Программист часто может сказать, просто взглянув на имена, аргументы и типы возвращаемых процедур (и соответствующие комментарии), что должна делать конкретная процедура, не обязательно обращая внимание на детали того, как она достигает своего результата. В то же время полная программа по-прежнему необходима, поскольку она в значительной степени фиксирует выполняемые операторы и порядок их выполнения.
Парадигма программирования, используемая для создания программ почти для всех компьютеров, обычно соответствует императивной модели. [примечание 1] Цифровое компьютерное оборудование предназначено для выполнения машинного кода , который является собственным для компьютера и обычно пишется в императивном стиле, хотя для некоторых архитектур, таких как Lisp-машины, существуют низкоуровневые компиляторы и интерпретаторы, использующие другие парадигмы .
С этой низкоуровневой точки зрения состояние программы определяется содержимым памяти, а операторы представляют собой инструкции на родном машинном языке компьютера. Императивные языки более высокого уровня используют переменные и более сложные операторы, но по-прежнему следуют той же парадигме. Рецепты и контрольные списки процессов , хотя и не являются компьютерными программами , также представляют собой знакомые концепции, по стилю схожие с императивным программированием; каждый шаг — это инструкция, и физический мир сохраняет это состояние. Поскольку основные идеи императивного программирования концептуально знакомы и непосредственно воплощены в аппаратном обеспечении, большинство компьютерных языков относятся к императивному стилю.
Операторы присваивания в императивной парадигме выполняют операцию над информацией, расположенной в памяти, и сохраняют результаты в памяти для последующего использования. Кроме того, императивные языки высокого уровня позволяют вычислять сложные выражения , которые могут состоять из комбинации арифметических операций и оценок функций , а также присваивать полученное значение памяти. Операторы цикла (например, циклы while , do while и for ) позволяют выполнять последовательность операторов несколько раз. Циклы могут либо выполнять содержащиеся в них операторы заранее определенное количество раз, либо выполнять их повторно, пока не будет выполнено какое-либо условие. Операторы условного ветвления позволяют выполнять последовательность операторов только при выполнении некоторого условия. В противном случае операторы пропускаются, и последовательность выполнения продолжается с следующего за ними оператора. Операторы безусловного ветвления позволяют перенести последовательность выполнения в другую часть программы. К ним относятся переход (называемый goto во многих языках), переключатель и вызов подпрограммы, подпрограммы или процедуры (который обычно возвращается к следующему оператору после вызова).
На раннем этапе разработки языков программирования высокого уровня введение блока позволило создавать программы, в которых группу операторов и объявлений можно было рассматривать как один оператор. Это, наряду с введением подпрограмм , позволило выражать сложные структуры путем иерархической декомпозиции в более простые процедурные структуры.
Многие императивные языки программирования (такие как Fortran , BASIC и C ) являются абстракциями языка ассемблера . [3]
Самыми ранними императивными языками были машинные языки первых компьютеров. Инструкции на этих языках были очень простыми, что упрощало аппаратную реализацию, но затрудняло создание сложных программ. FORTRAN , разработанный Джоном Бэкусом в International Business Machines (IBM) начиная с 1954 года, был первым крупным языком программирования, устранившим препятствия, создаваемые машинным кодом при создании сложных программ. ФОРТРАН был компилируемым языком , который допускал именованные переменные, сложные выражения, подпрограммы и многие другие функции, которые сейчас распространены в императивных языках. В следующие два десятилетия появилось множество других крупных императивных языков программирования высокого уровня. В конце 1950-х и 1960-х годах АЛГОЛ был разработан для облегчения выражения математических алгоритмов и даже служил целевым языком операционной системы для некоторых компьютеров. MUMPS (1966) довел императивную парадигму до логического предела, вообще не имея никаких операторов, полагаясь исключительно на команды, вплоть до того, что команды IF и ELSE стали независимыми друг от друга, связанными только внутренней переменной с именем $. ТЕСТ. COBOL (1960) и BASIC (1964) были попытками сделать синтаксис программирования более похожим на английский. В 1970-х годах Pascal был разработан Никлаусом Виртом , а C был создан Деннисом Ритчи , когда он работал в Bell Laboratories . Вирт продолжил разработку Модулы-2 и Оберона . Для нужд Министерства обороны США Джин Ичбиа и команда Honeywell начали разработку Ada в 1978 году , после 4-летнего проекта по определению требований к языку. Спецификация была впервые опубликована в 1983 году с изменениями в 1995, 2005 и 2012 годах.
В 1980-е годы наблюдался быстрый рост интереса к объектно-ориентированному программированию . Эти языки были императивными по стилю, но добавляли возможности для поддержки объектов . За последние два десятилетия 20-го века появилось множество таких языков. Smalltalk -80, первоначально задуманный Аланом Кеем в 1969 году, был выпущен в 1980 году Исследовательским центром Xerox в Пало-Альто ( PARC ). Опираясь на концепции другого объектно-ориентированного языка — Simula (который считается первым в мире объектно-ориентированным языком программирования , разработанным в 1960-х годах), — Бьёрн Страуструп разработал C++ , объектно-ориентированный язык, основанный на C. Разработка C++ началась в 1979 году, а первая реализация была завершена в 1983 году. В конце 1980-х и 1990-х годах известными императивными языками, основанными на объектно-ориентированных концепциях, были Perl , выпущенный Ларри Уоллом в 1987 году; Python , выпущенный Гвидо ван Россумом в 1990 году; Visual Basic и Visual C++ (включая Microsoft Foundation Class Library (MFC) 2.0), выпущенные Microsoft в 1991 и 1993 годах соответственно; PHP , выпущенный Расмусом Лердорфом в 1994 году; Java от Джеймса Гослинга ( Sun Microsystems ) в 1995 году, JavaScript от Брендана Эйха ( Netscape ) и Ruby от Юкихиро «Маца» Мацумото, оба выпущенные в 1995 году. Microsoft .NET Framework (2002) по своей сути является императивной, поскольку являются его основными целевыми языками: VB.NET и C# , которые на нем работают; однако функциональный язык Microsoft F# также работает на нем.
ФОРТРАН (1958 г.) был представлен как «Система ПЕРЕВОДА математических формул IBM». Он был разработан для научных расчетов без средств для работы со струнами . Наряду с объявлениями , выражениями и операторами он поддерживал:
Это удалось, потому что:
Однако поставщики, не принадлежащие IBM, также написали компиляторы Fortran, но с синтаксисом, который, скорее всего, не сработает с компилятором IBM. [4] Американский национальный институт стандартов (ANSI) разработал первый стандарт Фортрана в 1966 году. В 1978 году Фортран 77 стал стандартом до 1991 года. Фортран 90 поддерживает:
COBOL (1959) означает «Общий бизнес-ориентированный язык». Фортран манипулировал символами. Вскоре стало понятно, что символы не обязательно должны быть числами, поэтому были введены строки. [5] На разработку COBOL повлияло Министерство обороны США, при этом Грейс Хоппер внесла основной вклад. Заявления были английскими и многословными. Целью было разработать язык, на котором менеджеры могли бы читать программы. Однако отсутствие структурированных заявлений помешало достижению этой цели. [6]
Развитие COBOL строго контролировалось, поэтому не возникло диалектов, требующих стандартов ANSI. Как следствие, он не менялся в течение 15 лет до 1974 года. Версия 1990-х годов действительно внесла существенные изменения, такие как объектно-ориентированное программирование . [6]
АЛГОЛ (1960) означает «АЛГОРИТМИЧЕСКИЙ ЯЗЫК». Это оказало глубокое влияние на дизайн языков программирования. [7] Разработанный комитетом европейских и американских экспертов по языкам программирования, он использовал стандартные математические обозначения и имел удобочитаемый структурированный дизайн. Алгол был первым, кто определил свой синтаксис , используя форму Бэкуса-Наура . [7] Это привело к появлению компиляторов , управляемых синтаксисом . Он добавил такие функции, как:
Прямые потомки Алгола включают Паскаль , Модулу-2 , Аду , Дельфи и Оберон на одной ветке. В другой ветке есть C , C++ и Java . [7]
BASIC (1964) означает «Универсальный код символических инструкций для начинающих». Он был разработан в Дартмутском колледже для обучения всех его студентов. [8] Если бы ученик не перешел на более мощный язык, он все равно помнил бы Бейсик. [8] Интерпретатор Basic устанавливался в микрокомпьютеры , выпущенные в конце 1970-х годов. По мере роста индустрии микрокомпьютеров рос и язык. [8]
Компания Basic стала пионером интерактивных сеансов . [8] Он предлагал команды операционной системы в своей среде:
Однако синтаксис Basic был слишком простым для больших программ. [8] В последних диалектах добавлена структура и объектно-ориентированные расширения. Microsoft Visual Basic до сих пор широко используется и обеспечивает графический интерфейс пользователя . [9]
Язык программирования C (1973 г.) получил свое название потому, что язык BCPL был заменен на B , а компания AT&T Bell Labs назвала следующую версию «C». Его целью было написать операционную систему UNIX . [10] C — относительно небольшой язык, поэтому на нем легко писать компиляторы. Его рост отражал рост аппаратного обеспечения в 1980-х годах. [10] Его рост также обусловлен тем, что он имеет возможности языка ассемблера , но использует синтаксис высокого уровня . В него добавлены расширенные функции, такие как:
C позволяет программисту контролировать, в какой области памяти должны храниться данные. Для хранения глобальных переменных и статических переменных требуется наименьшее количество тактов . Стек автоматически используется для стандартных объявлений переменных . Куча памяти возвращается в переменную-указатель из malloc()
функции.
main()
. [12] Глобальные переменные видны main()
всем остальным функциям исходного кода.main()
, других функций или внутри {
}
разделителей блоков являются локальными переменными . Локальные переменные также включают переменные формальных параметров . Переменные параметров заключаются в круглые скобки определений функций. [13] Они обеспечивают интерфейс для функции.static
префикса, также хранятся в глобальной и статической области данных. [11] В отличие от глобальных переменных, статические переменные видны только внутри функции или блока. Статические переменные всегда сохраняют свое значение. Примером использования может быть функцияint increment_counter(){ static int counter = 0; counter++; return counter;}
static
префикса, включая переменные формальных параметров, [15] называются автоматическими переменными [12] и сохраняются в стеке. [11] Они видны внутри функции или блока и теряют свою область действия при выходе из функции или блока.malloc()
библиотечную функцию для выделения динамической памяти. [17] Заполнение кучи данными — это дополнительная функция копирования. Переменные, хранящиеся в куче, экономично передаются функциям с помощью указателей. Без указателей весь блок данных пришлось бы передавать в функцию через стек.В 1970-х годах инженерам-программистам требовалась языковая поддержка, чтобы разбивать большие проекты на модули . [18] Одной из очевидных особенностей было физическое разложение больших проектов на отдельные файлы . Менее очевидной особенностью было логическое разложение больших проектов на абстрактные типы данных . [18] В то время языки поддерживали конкретные ( скалярные ) типы данных, такие как целые числа, числа с плавающей запятой и строки символов . Конкретные типы данных имеют представление как часть своего имени. [19] Абстрактные типы данных представляют собой структуры конкретных типов данных с присвоенным новым именем. Например, список целых чисел можно назвать .integer_list
На объектно-ориентированном жаргоне абстрактные типы данных называются классами . Однако класс — это всего лишь определение; память не выделяется. Когда память выделяется классу, она называется объектом . [20]
Объектно-ориентированные императивные языки , разработанные путем объединения потребности в классах и необходимости безопасного функционального программирования . [21] В объектно-ориентированном языке функция присваивается классу. Назначенная функция тогда называется методом , функцией -членом или операцией . Объектно-ориентированное программирование — это выполнение операций над объектами . [22]
Объектно-ориентированные языки поддерживают синтаксис для моделирования отношений подмножества/надмножества . В теории множеств элемент подмножества наследует все атрибуты, содержащиеся в надмножестве . Например, студент – это человек. Следовательно, множество студентов является подмножеством множества людей. В результате студенты наследуют все признаки, общие для всех людей. Кроме того, студенты обладают уникальными качествами, которых нет у других людей. Объектно-ориентированные языки моделируют отношения подмножества/надмножества с помощью наследования . [23] Объектно-ориентированное программирование стало доминирующей языковой парадигмой к концу 1990-х годов. [18]
C++ (1985) первоначально назывался «C с классами». [24] Он был разработан для расширения возможностей C за счет добавления объектно-ориентированных средств языка Simula . [25]
Объектно-ориентированный модуль состоит из двух файлов. Файл определений называется файлом заголовка . Вот заголовочный файл C++ для класса GRADE в простом школьном приложении:
// оценка.h // -------// Используется, чтобы позволить нескольким исходным файлам включать // этот файл заголовка без ошибок дублирования. // См.: https://en.wikipedia.org/wiki/Imperative_programming/Include_guard // ------------------------------- --------------- #ifndef GRADE_H #define GRADE_Hclass GRADE { public : // Это операция конструктора. // ---------------------------------- GRADE ( const char Letter ); // Это переменная класса. // ------------------------- char Letter ; // Это операция-член. // --------------------------- intgrade_numeric ( const char Letter ) ; // Это переменная класса. // ------------------------- int numeric ; }; #endif
Операция конструктора — это функция с тем же именем, что и имя класса. [26] Он выполняется, когда вызывающая операция выполняет new
оператор.
Другой файл модуля — это исходный файл . Вот исходный файл C++ для класса GRADE в простом школьном приложении:
//grade.cpp // --------- #include "grade.h" GRADE :: GRADE ( const char Letter ) { // Ссылаемся на объект, используя ключевое слово 'this'. // ---------------------------------------------- этот - > буква = буква ; // Это временная сплоченность // ------------------------- this -> numeric = Grade_numeric ( Letter ); } int GRADE :: Grade_numeric ( const char Letter ) { if ( ( Letter == 'A' || Letter == 'a' ) ) return 4 ; else if ( ( буква == 'B' || буква == 'b' ) ) return 3 ; else if ( ( буква == 'C' || буква == 'c' ) ) return 2 ; else if ( ( буква == 'D' || буква == 'd' ) ) return 1 ; else if ( ( буква == 'F' || буква == 'f' ) ) return 0 ; иначе вернуть -1 ; }
Вот заголовочный файл C++ для класса PERSON в простом школьном приложении:
// person.h // -------- #ifndef PERSON_H #define PERSON_Hкласс PERSON { public : PERSON ( const char * name ); константный символ * имя ; }; #endif
Вот исходный файл C++ для класса PERSON в простом школьном приложении:
// person.cpp // ---------- #include "person.h" ЧЕЛОВЕК :: ЧЕЛОВЕК ( const char * name ) { this -> name = name ; }
Вот заголовочный файл C++ для класса STUDENT в простом школьном приложении:
// Student.h // --------- #ifndef STUDENT_H #define STUDENT_H#include "person.h" #include "grade.h" // СТУДЕНТ — это подмножество ЧЕЛОВЕКА. // -------------------------------- класс STUDENT : public PERSON { public : STUDENT ( const char * name ); ~ СТУДЕНТ (); ОЦЕНКА * оценка ; }; #endif
Вот исходный файл C++ для класса STUDENT в простом школьном приложении:
// Student.cpp // ----------- #include "student.h" #include "person.h" STUDENT :: STUDENT ( const char * name ) : // Выполняем конструктор суперкласса PERSON. // ---------------------------------------------------------------- - ЧЕЛОВЕК ( имя ) { // Больше нечего делать. // ------------------- } STUDENT ::~ STUDENT () { // освобождаем память оценки // во избежание утечек памяти. // ---------------------------------------------------------------- - удалить это -> оценка ; }
Вот программа-драйвер для демонстрации:
// Student_dvr.cpp // --------------- #include <iostream> #include "student.h" int main ( void ) { СТУДЕНТ * студент = новый СТУДЕНТ ( «Студент» ); студент -> оценка = новая GRADE ( 'a' ); std :: cout // Обратите внимание, что студент наследует имя ЧЕЛОВЕКА << студент -> имя << ": Числовая оценка = " << студент -> оценка -> числовая << " \n " ; // освобождаем память студента // чтобы избежать утечек памяти. // ---------------------------------------------------------------- - удалить студента ; вернуть 0 ; }
Вот make-файл для компиляции всего:
# makefile # -------- все : Student_dvr очистить : rm Student_dvr *.oстудент_двр : студент_двр . уровень cpp . о студент . о человек . o c++ Student_dvr.cpp Grade.o Student.o Person.o -o Student_dvr Grade.o : Оценка . уровень cpp . ч С++ -c ранг.cpp студент.о : студент . студент ЦПП . ч С++ -c студент.cpp person.o : человек . cpp человек . ч С++ -c person.cpp