Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ответы по экзамену ЯП.doc
Скачиваний:
3
Добавлен:
25.08.2019
Размер:
534.02 Кб
Скачать

Параметры-шаблоны

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

template< class Type, template< class > class Container >

class CrossReferences

{

Container< Type > mems;

Container< Type* > refs;

/* ... */

};

CrossReferences< Date, vector > cr1;

CrossReferences< string, set > cr2;

Нельзя использовать шаблоны функций в качестве параметров-шаблонов.

29. Структура как тип и совокупность данных в языке C++

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

    Тип данных struct - один из основных строительных блоков данных в языке. Он предоставляет удобный способ объединения различных элементов, связанных между собой.

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

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

struct date

{

int day;

int month;

int year;

int yearday;

char mon_name[5];

};

    Не следует полагать, что размер структуры равен сумме размеров ее членов. Вследствие выравнивания объектов разной длины в структуре могут появляться безымянные "дыры". Так, например, если переменная типа char занимает один байт, а int - четыре байта, то для структуры:

struct

{

char c;

int i;

}

может потребоваться восемь байт, а не пять. Правильное значение возвращает операция sizeof.

    Описание структуры, за которым не следует список объектов, не приводит к выделению памяти (как в программе выше); оно только определяет шаблон (форму) структуры. Однако, если такое описание снабжено именем типа (например, date), то это имя типа может быть использовано позднее при определении фактических экземпляров структур (определение структур d и f в программе).

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

date d = { 0,0,1776,186,"июл" };

date f = { 1,9,1986,0,"сент" }; .

    Автоматические структуры инициализации не подлежат!

    Элемент структуры может быть указан в выражении с помощью конструкции вида:

<имя структуры>.<имя элемента>.

    Структуры могут вкладываться одна в другую, но самовложение структур запрещено!

30. Объединения разнотипных данных в языке C++

одним важным типом представления данных являются объединения.

Это тип данных, который позволяет хранить различные типы данных в одной и той же области памяти. Объединение задается с помощью ключевого слова union подобно структуре. Покажем особенность применения объединений на примере хранения данных, которые могут быть и вещественными и целыми или представлять собой символ. Так как наперед неизвестно какой тип данных требуется сохранять, то в объединении необходимо задать три поля:

union tag_value {

int var_i;

double var_f;

char var_ch; 54

};

Данное объединение будет занимать в памяти 8 байт, ровно столько, сколько занимает переме нная самого большого объема, в данном случае var_f типа double. Все остальные переменные var_i и var_ch будут находиться в той же области памяти, что и переменная var_f. Благодаря такому подходу происходит экономия памяти, но платой за это является возможность хранения значения только одной переменной из трех в определенный момент времени.

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

Для того чтобы программа «знала» какой тип переменной содержит

объединение tag_value, необходимо ввести переменную, значение которой будет указывать номер используемой переменной: 0 – var_i, 1 – var_f и 2 – var_ch. Причем эту переменную удобно представить с объединением в виде структуры следующим образом:

struct tag_var {

union tag_value value;

short type_var;

};

Таким образом, получаем следующий алгоритм записи разнородной информации в объединение tag_value:

int main()

{

struct tag_var var[3];

var[0].type_var = 0;

var[0].value.var_i = 10;

var[1].type_var = 1;

var[1].value.var_f = 2.3;

var[2].type_var = 2;

var[2].value.var_ch = ‘d’;

for(int i = 0;i < 3;i++)

{

switch(var[i].type_var)

{

case 0:printf(“var = %d\n”,var[i].value.var_i);break;

case 1:printf(“var = %f\n”,var[i].value.var_f);break;

case 2:printf(“var = %c\n”,var[i].value.var_ch);break;

default: printf(“Значение переменной не определено\n”);

}

}

return 0;

}

Как видно из примера объединение tag_value позволяет эффективно, с точки зрения объема памяти, сохранять переменные разного типа. Выигрыш в55 объеме памяти для данного случая незначителен около 9-15 байт (в зависимости от стандарта языка С), но если бы таких переменных было 1000 и более, то выигрыш был бы ощутимым. В этом и заключается особенность объединений – экономия памяти при хранении данных.

31. Битовые поля структур и объединений в языке C++

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

struct { unsigned идентификатор 1 : длина-поля 1; unsigned идентификатор 2 : длина-поля 2; } длина - поля задается целым выражением или константой. Эта константа определяет число битов, отведенное соответствующему полю. Поле нулевой длинны обозначает выравнивание на границу следующего слова.

Пример:

struct { unsigned a1 : 1; unsigned a2 : 2; unsigned a3 : 5; unsigned a4 : 2; } prim;

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

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

32. Стадии и команды препроцессорной обработки в языке C++. Замены в тексте, директива #define

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

  1. Все системно-зависимые обозначения перекодируются в стандартные коды.

  2. Каждая пара из символов '\' и "конец строки" вместе с пробелами между ними убираются, и тем самым следующая строка исходного текста присоединяется к строке, в которой находилась эта пара символов.

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

  4. Выполняются директивы препроцессора и производятся макроподстановки.

  5. Эскейп-последовательности в символьных константах и символьных строках заменяются на их эквиваленты.

  6. Смежные символьные строки конкатинируются, то есть соединяются в одну строку.

  7. Каждая препроцессорная лексема преобразуется в текст на языке Си.

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

  • замена идентификаторов заранее подготовленными последовательностями символов;

  • включение в программу текстов из указанных файлов;

  • исключение из программы отдельных частей ее текста, условная компиляция;

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

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

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

Символические константы: #define

Директива #define может быть записана в двух синтаксических формах:

#define идентификатор текст #define идентификатор (список параметров) текст

Данная директива заменяет все дальнейшие вхождения идентификатора на текст. Подобный процесс называется макроподстановкой. Текст может быть любым фрагментом программы на СИ, а также может и отсутствовать. Если в качестве первого символа в строке программы используется символ #, то эта строка является командной строкой препроцессора (макропроцессора). Командная строка препроцессора заканчивается символом перевода на новую строку. Если непосредственно перед концом строки поставить символ обратной косой черты "\", токомандная строка будет продолжена на следующую строку программы. Директива #define, подобно всем директивам препроцессора, начинается c символа # в самой левой позиции. Она может появиться в любом месте исходного файла, а даваемое определение имеет силу от места появления до конца файла. Мы активно используем эту директиву для определения символических констант в наших примерах программ, однако она имеет более широкое применение, что мы покажем дальше.

33. Включение текстов файлов, директива #include

Директива #include включает в программу содержимое определенного файла. Эта директива может быть представлена в двух формах:

#include «имя файла» #include <имя файла>

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

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

34. Условная компиляция, директивы #if, #ifdef, #ifndef, #elif, #else, #endif.

Условная компиляция обеспечивается в языке C++ набором команд, которые, по существу, управляют не компиляцией, а препроцессорной обработкой:

#if <константное_выражение>

#ifdef <идентификатор>

#ifndef <идентификатор>

#else

#endif

#elif

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

    Общая структура применения директив условной компиляции такова:

#if/#ifdef/#ifndef <константное_выражение или идентификатор>

<текст_1>

#else //необязательная директива

<текст_2>

#endif

    Конструкция #else <текст_2> не обязательна. Текст_1 включается в компилируемый текст только при истинности проверяемого условия. Если условие ложно, то при наличии директивы #else на компиляцию передается текст_2. Если директива #else отсутствует, то весь текст от #if до #endif при ложном условии опускается. Различие между формами команд #if состоит в следующем.

    В первой из перечисленных директив #if проверяется значение константного целочисленного выражения. Если оно отлично от нуля, то считается, что проверяемое условие истинно. Например, в результате выполнения директив:

#if 5+12

<текст_1>

#endif

текст_1 всегда будет включен в компилируемую программу.

    В директиве #ifdef проверяется, определен ли с помощью команды #define к текущему моменту идентификатор, помещенный после #ifdef. Если идентификатор определен, то текст_1 используется компилятором.

    В директиве #ifndef проверяется обратное условие - истинным считается неопределенность идентификатора, т.е. тот случай, когда идентификатор не был использован в команде #define или его определение было отменено командой#undef.

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

#define DE 1 //Определение идентификатора.

. . . . .

#ifdef DE //Проверка определения идентификатора.

cout << "Отладочная печать";

#endif

    Таких печатей, появляющихся в программе в зависимости от определенности идентификатора DE может быть несколько и, убрав директиву #define DE 1, сразу же отключаем все отладочные печати.

    Файлы, предназначенные для препроцессорного включения в модули программы, обычно снабжают защитой отповторного включения. Такое повторное включение может произойти, если несколько модулей, в каждом из которых запланировано препроцессорное включение одного и того же файла, объединяются в общий текст программы. Например, такими средствами защиты снабжены все заголовочные файлы (подобные iostream.h) стандартной библиотеки. Схема защиты от повторного включения может быть такой (фрагмент файла с именем filename):

#ifndef _FILE_NAME //Если константа не определена

//Включаемый текст файла filename.

#define _FILE_NAME //Определение идентификатора.

#endif

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

    Для организации мультиветвлений во время обработки препроцессором исходного текста программы введена директива

#elif <константное_выражение>

которая является сокращением конструкции #else #if.

    Структура исходного текста с применением этой директивы такова:

#if <константное_выражение_1>

<текст_1>

#elif <константное_выражение_2>

<текст_2>

#elif <константное_выражение_3>

<текст_3>

. . . .

#else

<текст_N>

#endif

    Препроцессор проверяет вначале условие в директиве #if, если оно ложно (равно 0) - вычисляетконстантное_выражение_2, если оноравно О - вычисляется константное_выражение_3 и т.д. Если все выражения ложны, то в компилируемый текст включается текст для случая #else. В противном случае, т.е. при появлении хотя бы одного истинного выражения (в #if или в #elif), начинает обрабатываться текст, расположенный непосредственно за этой директивой, а все остальные директивы не рассматриваются. Таким образом, препроцессор обрабатывает всегда только один из участков текста, выделенных командами условной компиляции.

35. Макроподстановки средствами препроцессора. Директивы # и ##

Пустая директива состоит из строки, в которой содержится единственный символ #. Эта директива всегда игнорируется препроцессором.

36. Парадигмы программирования, объектная модель.

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

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

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

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

  1. наличием объектов и возможностью описания динамики системы как реакции на сообщения, пересылаемыми между объектами;

  2. возможностью создания новых классов;

  3. наличием механизма наследования для расширения или уточнения поведения имеющихся классов;

  4. наличием полиморфизма между классами или объектами.

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

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

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

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

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

• Абстракция сущности Объект представляет собой полезную модель некой сущности в предметной области

• Абстракция поведения Объект состоит из обобщенного множества операций

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

  • Пользователь может взаимодействовать с объектом только через этот интерфейс. Реализуется с помощью ключевого слова: public.

  • Пользователь не может использовать закрытые данные и методы. Реализуется с помощью ключевых слов: private, protected, internal.

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

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

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

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

Модульность — это свойство системы, которая была разложена на внутренне связные, но слабо связные между собой модули.

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

Иерархия — это упорядочение абстракций путем расположения их по уровням. В объектно-ориентированных системах используются два вида иерархических структур: структуры классов ( иерархические отношения «is a») и структуры объектов (отношения вида «part of»).

Отношения вида «is a» реализуются в объектно-ориентированных языках с помощью наследования или генерализации.

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

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

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

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

Параллелизм — это свойство, отличающее активные объекты от пассивных.

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

Параллелизм может обеспечиваться как средствами языка (Java, Ada,

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

38. Преимущества объектной модели

Существует пять преимуществ, которые дает объектная модель.  Во-первых, объектная модель позволяет в полной мере использовать выразительные возможности объектных и объектно-ориентированных языков программирования. Страуструп отмечает: "Не всегда очевидно, как в полной мере использовать преимущества такого языка, как C++. Существенно повысить эффективность и качество кода можно просто за счет использования C++ в качестве "улучшенного C" с элементами абстракции данных. Однако гораздо более значительным достижением является введение иерархии классов в процессе проектирования. Именно это называется OOD и именно здесь преимущества C++ демонстрируются наилучшим образом" [82]. Опыт показал, что при использовании таких языков, как Smalltalk, Object Pascal, C++, CLOS и Ada вне объектной модели, их наиболее сильные стороны либо игнорируются, либо применяются неправильно.  Сохраняемость поддерживает состояние и класс объекта в пространстве и во времени. Во-вторых, использование объектного подхода существенно повышает уровень унификации разработки и пригодность для повторного использования не только программ, но и проектов, что в конце концов ведет к созданию среды разработки [83]. Объектно-ориентированные системы часто получаются более компактными, чем их не объектно-ориентированные эквиваленты. А это означает не только уменьшение объема кода программ, но и удешевление проекта за счет использования предыдущих разработок, что дает выигрыш в стоимости и времени.  В-третьих, использование объектной модели приводит к построению систем на основе стабильных промежуточных описаний, что упрощает процесс внесения изменений. Это дает системе возможность развиваться постепенно и не приводит к полной ее переработке даже в случае существенных изменений исходных требований.  В-четвертых, в главе 7 показано, как объектная модель уменьшает риск разработки сложных систем, прежде всего потому, что процесс интеграции растягивается на все время разработки, а не превращается в единовременное событие. Объектный подход состоит из ряда хорошо продуманных этапов проектирования, что также уменьшает степень риска и повышает уверенность в правильности принимаемых решений.  Наконец, объектная модель ориентирована на человеческое восприятие мира, или, по словам Робсона, "многие люди, не имеющие понятия о том, как работает компьютер, находят вполне естественным объектно-ориентированный подход к системам".

39. Объектно-ориентированный подход к проектиро­ванию и разработке программ

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

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

  • Из каких частей состоит система.

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

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

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

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

40. Понятия «объект» и «класс». Свойства объекта: состояние, поведение, идентичность

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

Введение объектов преследует две цели:

    • понимание прикладной задачи (проблемы); введение основы для реализации на компьютере.

Каждый объект имеет определенное время жизни.

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

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

Каждый объект имеет состояние, обладает четко определенным поведением и уникальной идентичностью.

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

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

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

Пример: операции с файлом.

Результат выполнения действий зависит от состояния объекта на момент совершения действия, т.е. нельзя, например, удалить файл, если он открыт кем-либо (заблокирован).

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

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

В терминологии объектно-ориентированного подхода понятия «действие», «сообщение» и «метод» являются синонимами.

Поведение (behavior) — действия и реакции объекта, выраженные в терминах передачи сообщений и изменения состояния; видимая извне и воспроизводимая активность объекта.

Уникальность — это то, что отличает объект от других объектов.

Пример: банкноты.

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

Уникальность объекта состоит в том, что всегда можно определить, указывают две ссылки на один и тот же объект или на разные объекты.

Все банкноты принадлежат одному и тому же классу объектов (именно с этим связана их одинаковость). Номинальная стоимость, материал, форма — это атрибуты класса.

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

Все объекты одного и того же класса описываются одинаковыми наборами атрибутов. Однако объединение объектов в классы определяется не наборами, а семантикой.

«Конюшня» и «лошадь» могут иметь одинаковые атрибуты: цена и возраст.

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

В соответствии с UML (Unified Modelling Language — унифицированный язык моделирования), класс изображается в виде прямоугольника, состоящего из трех частей. В верхней части помещается название класса, в средней — свойства объектов класса, в нижней — действия, которые можно выполнять с объектами данного класса (методы).

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

    • конструктор (constructor) - выполняется при создании объектов;

- деструктор (destructor) - выполняется при уничтожении объектов.

41. Класс как расширение понятия структуры. Данные и методы класса в языке C++

Операция - это функция (или преобразование), которую можно применять к объектам данного класса.

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

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

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

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

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

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

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

42. Доступность компонентов класса в языке C++. Статические компоненты класса

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

 

Свойства (properties) компонент представляют собой расширение понятия членов данных и хотя не хранят данные как таковые, однако обеспечивают доступ к членам данных объекта. C++Builder использует ключевое слово _property для объявления свойств. При помощи событий (events) компонента сообщает пользователю о том, что на нее оказано некоторое предопределенное воздействие. Основная сфера применения методов в программах, разрабатываемых в среде C++Builder -это обработчики событий (event handlers), которые реализуют реакцию программы на возникновение определенных событий. Легко заметить некоторое сходство событий и сообщений операционной системы Windows. Типичные простые события —нажатие кнопки или клавиши на клавиатуре. Компоненты инкапсулируют свои свойства, методы и события.

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

• Большинство компонент представляют собой элементы управления интерфейсом с пользователем, причем некоторые обладают весьма сложным поведением.

• Все компоненты являются прямыми или косвенными потомками одного общего класса-прародителя (TComponent).

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

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

• Свойства компонент заключают в себе RTTI - идентификацию динамических типов.

• Компоненты можно добавлять к Палитре компонент и далее манипулировать с ними посредством Редактора форм интегрированной среды визуальной разработки C++Builder.

ООП интерпретирует взаимодействие с объектами как посылку запросов некоторому объекту или между объектами. Объект, принявший запрос, реагирует вызовом соответствующего метода. В отличие от других языков ООП, таких как SmallTalk, C++ не поощряет использование понятия "запрос". Запрос - это то, что делается с объектом, а метод - это то, как объект реагирует на поступивший запрос.

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

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

43. Указатель this в языке C++

Указатель this является константным только в const методах. Нужен для того, чтобы в методе не возникало неопределенности когда поле класса и параметры метода имеют одиноковое имя . this -> x = x; Данные у каждого объекта свои, а функции класса общие для всех объектов, указатель this помогает, определить с данными какого объекта будет работать функция. Вот этот-то самый указатель, именующий индивидуальную структуру данных для каждого объекта и называется "указатель this". Семантически он соответствует адресу "всего экземпляра объекта". И когда компилятор компилирует код метода класса, то все ссылки на свои данные экземпляра (например, на переменную "x") компилятор по умолчанию трактует как this->x. Т.о. внутри метода класса this есть "указатель на самого себя". Естественно, что внутри каждого экземпляра всякого класса this будет иметь своё значение - его заранее не знает программист, зато - очень хорошо знает компилятор. И его обязательно получит и любой нестатический метод объекта.

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

44. Друзья классов в языке C++

Предположим, вы определили два класса, vector и matrix (вектор и матрица). Каждый скрывает свое представление и предоставляет полный набор действий для манипуляции объектами его типа. Теперь определим функцию, умножающую матрицу на вектор. Для простоты допустим, что в векторе четыре элемента, которые индексируются 0...3, и что матрица состоит из четырех векторов, индексированных 0...3. Допустим также, что доступ к элементам вектора осуществляется через функцию elem(), которая осуществляет проверку индекса, и что в matrix имеется аналогичная функция. Один подход состоит в определении глобальной функции multiply() (перемножить) примерно следующим образом:

vector multiply(matrix& m, vector& v);

{

vector r;

for (int i = 0; i<3; i++) { // r[i] = m[i] * v;

r.elem(i) = 0;

for (int j = 0; j<3; j++)

r.elem(i) += m.elem(i,j) * v.elem(j);

}

return r;

}

Это своего рода "естественный" способ, но он очень неэффективен. При каждом обращении к multiply() elem() будет вызываться 4*(1+4*3) раза.

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

class matrix;

class vector {

float v[4];

// ...

friend vector multiply(matrix&, vector&);

};

class matrix {

vector v[4];

// ...

friend vector multiply(matrix&, vector&);

};

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

vector multiply(matrix& m, vector& v);

{

vector r;

for (int i = 0; i<3; i++) { // r[i] = m[i] * v;

r.v[i] = 0;

for (int j = 0; j<3; j++)

r.v[i] += m.v[i][j] * v.v[j];

}

return r;

}

Есть способы преодолеть эту конкретную проблему эффективности не используя аппарат friend (можно было бы определить операцию векторного умножения и определить multiply() с ее помощью). Однако существует много задач, которые проще всего решаются, если есть возможность предоставить доступ к закрытой части класса функции, которая не является членом этого класса. В Главе 6 есть много примеров применения friend. Достоинства функций друзей и членов будут обсуждаться позже. Функция член одного класса может быть другом другого. Например:

class x {

// ...

void f();

};

class y {

// ...

friend void x::f();

};

Нет ничего необычного в том, что все функции члены одного класса являются друзьями другого. Для этого есть даже более краткая запись:

class x {

friend class y;

// ...

};

Такое описание friend делает все функции члены класса y друзьями x.

45. Понятие конструкторов и деструктора класса в языке C++

С++ обеспечивает удобные способы создания и удаления объектов, через специальные процедуры. Процедуры создания объектов называются конструкторами, а процедуры уничтожения - деструкторами. Конструкторы автоматически вызываются при описании объекта, а деструкторы - при выходе из блока, в котором этот объект был описан. Если необходимые конструкторы или деструктор для класса не описаны, то транслятор создает их сам. Для того, чтобы понять важность конструкторов, сделаем маленькое отступление. Общей проблемой для всех традиционных языков программирования является инициализация: прежде чем использовать структуру данных, нужно выделить ей память и проинициализировать. 

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

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

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

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

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

46. Виды конструкторов в языке C++

В объектно-ориентированном программировании конструктор класса (от англ. constructor, иногда сокращают ctor) — специальный блок инструкций, вызываемый при создании объекта.

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

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

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

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

  • конструктор по умолчанию — конструктор, не принимающий аргументов;

  • конструктор копирования — конструктор, принимающий в качестве аргумента объект того же класса (или ссылку из него);

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