1. Эволюция технологий программирования. Языки семейства С.1.1. Исторический экскурс. Языки программирования имеют дело с двумя основными понятиями: данными и алгоритмами. Данные представляют собой информацию, которую программа обрабатывает. А алгоритмы — это методы, которые программа использует (для обработки данных). Язык С, как и большинство основных языков программирования нашего времени, является процедурным. Это означает, что основное внимание в нем уделяется алгоритмам. Теоретически процедурное программирование заключается в том, что сначала определяется последовательность действий, которая должна быть выполнена компьютером, а затем эти действия реализуются с помощью языка программирования. Программа содержит набор процедур, которые компьютер должен выполнить, чтобы получить требуемый результат. По мере того как программы становились все больше, первые процедурные языки, такие как FORTRAN и BASIC, столкнулись с проблемами организационного плана. Например, в программах часто используются инструкции ветвления, которые направляют ход выполнения программы в- сторону того или иного набора операторов в зависимости от результатов некоторой проверки. У многих старых программ такой запутанный ход выполнения (их называют "программы-спагетти"), что понять их, читая, по существу, невозможно, а модификация такой программы может привести к настоящей катастрофе. В ответ на это ученые-"компьютерщики" разработали более упорядоченный стиль программирования, который называется структурное программирование. Язык С включает ряд элементов, облегчающих применение структурного программирования. Например, структурное программирование ограничивает возможности ветвления (выбора следующего выполняемого оператора) небольшим набором хорошо функционирующих конструкций. Эти конструкции (циклы for, while, do while и оператор if else) входят в словарь языка С. Еще одним из новых принципов программирования было проектирование программ сверху вниз, которое заключается в разбиении большой программы на меньшие, легче программируемые задачи. Если одна из этих задач по-прежнему остается слишком обширной, разделите ее также на более мелкие задачи. Продолжайте этот процесс до тех пор, пока программа не будет разделена на маленькие, легко программируемые модули. Язык С облегчает такой подход, поощряя программистов разрабатывать программные единицы (элементы), называемые функциями, которые представляют собой модули отдельных задач. Как вы могли заметить, методика структурного программирования отражает процедурный подход, при котором программа рассматривается с точки зрения выполняемых ею действий.1.2. Объектно-ориентированное программирование. Хотя принципы структурного программирования улучшили понятность и надежность программ, а также облегчили их сопровождение, создание программ больших размеров по-прежнему оставалось трудной задачей. Объектно-ориентированное программирование (ООП) приносит с собой новый подход к решению этой задачи. В отличие от процедурного программирования, где главное внимание уделяется алгоритмам, в ООП основное внимание направлено на данные. При использовании ООП проблему не решают с помощью процедурного подхода, заложенного в языке, а приспосабливают язык для решения этой проблемы. Идея заключается в создании таких форм данных, которые соответствовали бы основным характерным чертам проблемы. В языке C++ класс является спецификацией, описывающей такую новую форму данных, а объект — конкретной структурой данных, созданной в соответствии с этой спецификацией. Например, класс может описывать общие свойства руководящего работника корпорации (имя, должность, жалование и, например, необычные способности), тогда как объект представляет конкретного руководителя (Гилфорд Шипблат (Guilford Sheepblat), вице-президент, оклад $325 тыс. в год, знает как пользоваться файлом CONFIG.SYS). В общем, класс определяет, какие данные будут образовывать объект и какие операции могут выполняться над этими данными. Например, предположим, что вы разрабатываете графическую программу, способную рисовать прямоугольники. Можно создать класс, описывающий прямоугольник. Данными в спецификации этого класса может быть следующее: местоположение углов, высота и ширина, цвет и стиль ограничивающей линии, а также цвет и шаблон, используемые для заполнения прямоугольника. Часть спецификации этого класса, описывающая операции, может включать методы перемещения прямоугольника, изменения его размеров, вращения треугольника, изменения цветов и шаблонов, а также копирования прямоугольника в другое место. Если затем использовать эту программу, чтобы нарисовать прямоугольник, то она создаст объект в соответствии со спецификацией класса. Этот объект будет содержать все значения данных, описывающие прямоугольник, а с помощью методов класса можно будет этот прямоугольник модифицировать. Если необходимо нарисовать два прямоугольника, то программа создаст два объекта, по одному для каждого прямоугольника. Объектно-ориентированный подход к разработке программы состоит в том, что сначала разрабатываются классы, точно представляющие те вещи, с которыми имеет дело программа. В графической программе, например, можно определить классы для представления прямоугольников, линий, окружностей, кистей, ручек и т.п. Вспомните, что определение класса включает описание допустимых операций для каждого класса, таких как перемещение окружности или вращение линии. После этого можно приступать к разработке самой программы, используя объекты этих классов. Этот процесс продвижения от более низкого уровня организации, такого как классы, к более высокому уровню, такому как программа, называется программированием снизу вверх. Объектно-ориентированное программирование — это не только связывание данных и методов в единое целое: определение класса. ООП, например, облегчает создание повторно используемого кода программы, что в конечном итоге освобождает от большого объема работы. Сокрытие информации позволяет предохранить данные от нежелательного доступа. Полиморфизм дает возможность создавать множественные определения для операций и функций, а то, какое определение будет использоваться, зависит от контекста программы. Наследование позволяет создавать новые классы из старых. С объектно-ориентированным программированием позволяется много новых идей и используется иной подход к созданию программ, чем при процедурном программировании. Вместо того чтобы сосредоточить свое внимание на задачах, вы фокусируете его на представлении различных понятий. Вместо того чтобы использовать программирование "сверху-вниз", иногда приходится использовать программирование "снизу-вверх". Разработка полезного и надежного класса может быть трудной задачей. К счастью, объектно-ориентированные языки дают возможность без особого труда включать существующие классы в создаваемые программы. Поставщики программного обеспечения разработали различные библиотеки классов, включая библиотеки классов, предназначенные для упрощения создания программ в таких средах, как Windows или Macintosh. Одним из реальных преимуществ языка C++ является то, что он позволяет легко адаптировать и повторно использовать хорошо проверенные коды программ. Основные понятия объектно-ориентированного программирования: Абстрагирование – это способ выделить набор значимых характеристик объекта, исключая из рассмотрения незначимые. Соответственно, абстракция – это набор всех таких характеристик. Инкапсуляция – это свойство системы, позволяющее объединить данные и методы, работающие с ними, в классе и скрыть детали реализации от пользователя. Наследование – это свойство системы, позволяющее описать новый класс на основе уже существующего с частично или полностью заимствующейся функциональностью. Класс, от которого производится наследование, называется базовым или родительским. Новый класс – потомком, наследником или производным классом. Полиморфизм – это свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.1.3. Обобщенное программирование. Обобщенное программирование — это еще одна парадигма программирования, поддерживаемая языком C++. Оно имеет общую с ООП цель — упростить повторное использование кодов программ и методов абстрагирования общих понятий. Однако, в то время как в ООП основное внимание уделяется данным, в обобщенном программировании упор делается на алгоритмы. И у него другая область применения. ООП — это инструмент для разработки больших проектов, тогда как обобщенное программирование предоставляет инструменты для выполнения задач общего характера, таких как сортировка данных или объединение списков. Обобщённое программирование — парадигма программирования, заключающаяся в таком описании данных и алгоритмов, которое можно применять к различным типам данных, не меняя само это описание. В языке C++ имеются данные различных типов — целые числа, числа с дробной частью, символы, строки символов, определяемые пользователем сложные структуры, состоящие из данных нескольких типов. Если, например, требуется сортировать данные различных типов, то обычно для каждого типа создается отдельная функция сортировки. Обобщенное программирование расширяет язык таким образом, что позволяет один раз написать функцию для обобщенного (т.е. неопределенного) типа данных и затем использовать ее для разнообразных реальных типов данных. Это обеспечивается с помощью шаблонов языка C++. Средства обобщённого программирования реализуются в языках программирования в виде тех или иных синтаксических средств, дающих возможность описывать данные (типы данных) и алгоритмы (процедуры, функции, методы), параметризуемые типами данных. У функции или типа данных явно описываются формальные параметры-типы. Это описание является обобщённым и в исходном виде непосредственно использовано быть не может. В тех местах программы, где обобщённый тип или функция используется, программист должен явно указать фактический параметр-тип, конкретизирующий описание. Например, обобщённая процедура перестановки местами двух значений может иметь параметр-тип, определяющий тип значений, которые она меняет местами. Когда программисту нужно поменять местами два целых значения, он вызывает процедуру с параметром-типом «целое число» и двумя параметрами — целыми числами, когда две строки — с параметром-типом «строка» и двумя параметрами — строками. В случае с данными программист может, например, описать обобщённый тип «список» с параметром-типом, определяющим тип хранимых в списке значений. Тогда при описании реальных списков программист должен указать обобщённый тип и параметр-тип, получая, таким образом, любой желаемый список с помощью одного и того же описания. Компилятор, встречая обращение к обобщённому типу или функции, выполняет необходимые процедуры статического контроля типов, оценивает возможность заданной конкретизации и при положительной оценке генерирует код, подставляя фактический параметр-тип на место формального параметра-типа в обобщённом описании. Для успешного использования обобщённых описаний фактические типы-параметры должны удовлетворять определённым условиям. Если обобщённая функция сравнивает значения типа-параметра, любой конкретный тип, использованный в ней, должен поддерживать операции сравнения, если присваивает значения типа-параметра переменным — конкретный тип должен обеспечивать корректное присваивание. Известно два основных способа реализации поддержки обобщённого программирования в компиляторе. 1. Порождение нового кода для каждой конкретизации. В этом варианте компилятор рассматривает обобщённое описание как текстовый шаблон для создания вариантов конкретизаций. Когда компилятору требуется новая конкретизация обобщённого типа или процедуры, он создаёт новый экземпляр типа или процедуры, чисто механически добавляя туда тип-параметр. То есть, имея обобщённую функцию перестановки элементов, компилятор, встретив её вызов для целого типа, создаст функцию перестановки целых чисел и подставит в код её вызов, а затем, встретив вызов для строкового типа — создаст функцию перестановки строк, никак не связанную с первой. Этот метод обеспечивает максимальное быстродействие, поскольку варианты конкретизаций становятся разными фрагментами программы, каждый из них может быть оптимизирован для своего типа-параметра, к тому же в код не включаются никакие лишние элементы, связанные с проверкой или преобразованием типов на этапе исполнения программы. Недостатком его является то, что при активном использовании обобщённых типов и функций с различными типами-параметрами размер откомпилированной программы может очень сильно возрастать, поскольку даже для тех фрагментов описания, которые для разных типов не различаются, компилятор всё равно генерирует отдельный код. Этот недостаток можно затушевать путём частичной генерации общего кода (часть обобщённого описания, которая не зависит от типов-параметров, оформляется специальным образом и по ней компилятор генерирует единый для всех конкретизаций код). Зато данный механизм даёт естественную возможность создания специальных (обычно — сильно вручную оптимизированных) конкретизаций обобщённых типов и функций для некоторых типов-параметров. 2. Порождение кода, который во время исполнения выполняет преобразование фактических параметров-типов к одному типу, с которым фактически и работает. В этом случае на этапе компиляции программы компилятор лишь проверяет соответствие типов и включает в код команды преобразования конкретного типа-параметра к общему типу. Код, определяющий функционирование обобщённого типа или функции, имеется в откомпилированной программе в единственном экземпляре, а преобразования и проверки типов выполняются динамически, во время работы программы. В этом варианте порождается, как правило, более компактный код, но программа оказывается в среднем медленнее, чем в первом варианте, из-за необходимости выполнения дополнительных операций и меньших возможностей оптимизации. Кроме того, в компилированный код для типов-параметров далеко не всегда включается динамическая информация о типах (в первом варианте она есть, если вообще поддерживается, поскольку конкретизации для каждого типа-параметра различны), что определяет некоторые ограничения на применение обобщённых типов и функций. В языке C++ обобщённое программирование основывается на понятии «шаблон», обозначаемом ключевым словом template. Широко применяется в стандартной библиотеке C++ (STL. // Описание шаблонной функции template T max(T x, T y) { if (x return y; else return x; } ... // Применение шаблонной функцииint a = max(10,15); ... double f = max(123.11, 123.12); ... Стандартная библиотека шаблонов (STL) (англ. Standard Template Library) — набор согласованных обобщённых алгоритмов, контейнеров, средств доступа к их содержимому и различных вспомогательных функций. В библиотеке выделяют пять основных компонентов: Контейнер (container) - хранение набора объектов в памяти. Итератор (iterator) - обеспечение средств доступа к содержимому контейнера. Алгоритм (algorithm) - определение вычислительной процедуры. Адаптер (adaptor) - адаптация компонентов для обеспечения различного интерфейса. Функциональный объект (functor) - сокрытие функции в объекте для использования другими компонентами. Разделение позволяет уменьшить количество компонентов. Например, вместо написания отдельной функции поиска элемента для каждого типа контейнера обеспечивается единственная версия, которая работает с каждым из них, пока соблюдаются основные требования. На базовом уровне вы просто используете обобщенные классы - обычно это коллекции, причем делаете это, не задумываясь о том, как они работают. Большинство прикладных программистов предпочитают оставаться на этом уровне до тех пор, пока что-то не пойдет не так. Вы можете столкнуться с непонятным сообщением об ошибке, смешивая разные обобщенные классы, или же имея дело с унаследованным кодом, который ничего не знает о параметрах типа. В такой момент вам нужно знать достаточно об обобщениях , чтобы решить проблему системно, а не "методом тыка". И, наконец, конечно, вы можете решить реализовать свои собственные обобщенные классы и методы Однако реализовать обобщенный класс не так просто. Программисты, которые будут использовать ваш код, попытаются подставлять всевозможные классы вместо ваших параметров типа. Они ожидают, что все будет работать без досадных ограничений и запутанных сообщений об ошибках. Ваша задача как обобщенного программиста - предвидеть все возможные будущие применения вашего класса.