
- •1. Понятие модуля. Принципы модульного программирования. Понятие объекта как динамического модуля.
- •2. Понятие класса. Понятие метода. Представление метода в виде обычной процедуры. Понятие конструктора и деструктора.
- •3. Понятие свойства. Методы получения и установки значений свойств. Свойства-массивы (в некоторых языках программирования). Индексаторы (в некоторых языках программирования).
- •Информация о типе времени выполнения программы
- •5. Классы в программных модулях. Атрибуты доступа к элементам объектов. Термин «инкапсуляция».
- •Термин «инкапсуляция»
- •Virtual – в базовом классе
- •7. Понятие ссылки на метод объекта (или делегата – в зависимости от языка программирования). Понятие события. Применение ссылок на методы для расширения объектов.
- •8. Понятие метакласса (в некоторых языках программирования). Методы, применяемые к классам. Виртуальные конструкторы (в некоторых языках).
- •Понятие метакласса (в некоторых языках программирования)
- •Виртуальные конструкторы (в некоторых языках)
- •3) Finally – вызвать free.
- •Если глобально-уникальный идентификатор назначается интерфейсу, то он записывается после ключевого слова interface и заключается в квадратные скобки, например:
- •Microsoft Visual Studio
- •Import Network;
- •Var Stat: SocketStat;
- •Var SocketStatCollection: … ;
- •Конструкторы и деструкторы
- •Стандартные конструкторы
- •Создание объектов по значению (на стеке) и по ссылке (в динамической памяти)
- •Операторы new и delete
- •Размещающий оператор new
- •Порядок конструирования и разрушения объектов
- •Вложенные определения классов
- •«Друзья» класса
- •Статические члены класса
- •19. Перегрузка бинарных операторов. Перегрузка унарных операторов. Перегрузка операторов преобразования типа.
- •Индексаторы
- •Механизм вызова событий
- •Создание пользовательских обобщенных коллекций
- •Создание обобщенных интерфейсов
- •Несколько слов о вложенных делегатах
- •25. Понятие итератора в языке c#. Оператор foreach. Оператор yield.
- •И напоследок... Блок finally
- •26. Понятие атрибутов в языке c#. Создание пользовательских атрибутов. Анализ атрибутов во время выполнения программы. Понятие рефлексии (reflection) в языке c#. Сериализация объектов.
- •Что такое метаданные и зачем они нужны?
- •1. Метаданные в .Net обязательны и универсальны.
- •2. Метаданные в .Net общедоступны.
- •3. Метаданные в .Net исчерпывающи.
- •4. Метаданные в .Net расширяемы.
- •5. Метаданные в .Net конструируемы программно.
- •Получение экземпляра класса Type
- •Динамическая загрузка сборок
- •Динамическая загрузка типов
- •Исследование типа
- •Характеристики типа как целого
- •Члены класса
- •Исследование объекта
- •Динамическое создание объекта и вызов методов
- •Создание объекта по его типу
- •Декларативное программирование
- •Новые механизмы абстракции?
- •Динамическое создание типов
- •Роль графов объектов
- •Formatter сериализации
- •XmlSerializer
- •Интерфейсы iFormatter и iRemotingFormatter
- •Точность типов среди форматеров
- •28*. Ооп в языке программирования Smalltalk. Достоинства и недостатки этого языка в сравнениии с языком программирования c#.
Import Network;
TYPE SocketStat = POINTER TO RECORD
CurrentSpeed : INTEGER;
…
END;
PROCEDURE Network Send+ (S: Network.Socket; Data: String);
Var Stat: SocketStat;
BEGIN
BASE(S, Data);
Stat := FindSocketStat(S);
Stat.CurrentSpeed := … ;
END;
END NetworkMonitor;
TYPE SocketStat = POINTER TO RECORD (Socket)
…
END;
PROCEDURE … (S:Network.Socket; Data: String);
BEGIN
BASE(S,Data);
S(SocketStat).CurrentSpeed := … ; END;
В модуле Network создаются экземпляры типа Socket, а не SocketStat. Чтобы создать экземпляр нового типа, нужно использовать паттерны (Factory Pattern).
Если расширяющих модулей несколько и все они создают свои типы на основе типа Socket, то Factory Method не работает.
Ассоциирование данных
MODULE NetworkMonitor;
Var SocketStatCollection: … ;
PROCEDURE Network Send+ (S, Data)
BEGIN
BASE(S, DATA);
FindSocketStat(S).CurrentSpeed; END;
Минусы: высокая трудоемкость (поддерживать ассоциацию при create и удалении первичных объектов). При уничтожении socket нужно уничтожить socketstat. => снижение надежности, уменьшение производительности, необходимость синхронизации – при доступе к таблицам поиска необходимо синхронизировать потоки;
РЕШЕНИЕ: дополнение типов данных.
MODULE NetworkMonitor;
IMPORT Network;
TYPE
SocketStat = POINTER TO RECORD EXTENSION Network.Socket
…
END;
PROCEDURE NetworkSend+ (…)
BEGIN BASE(S,Data);
S[SocketStat].CurrentSpeed;
END;
Реализация: 1) дополнение расширяет все экземпляры определенного типа данных;
2) память под дополнение выделяются на лету – при первом обращении к объекту;
3) дополнения можно использовать только для данных, которые находятся в динамической памяти;
4) ортогональны «наследованию», могут создаваться в любом количестве и для любого типа данных в иерархии;
Эти методы позволяют:
1) упростить создание, сопровождение и развитие расширяемых систем;
2) повысить надежность;
3) повысить производительность;
4) обеспечить статическую и динамическую верификацию правил расширения системы.
АОП – принципы программирования. АОП зависит от ООП.
Oberon
Oberon: разрабатывался для создания маленьких систем управления, ОС. Oberon System для Lilith PC.
Понятие объекта не вводится, но свой язык Вирт называет ООП-языком.
Главное свойство – свойство расширяемости (наследование типов, но виртуальные методы отсутствуют. Есть записи и их наследование. )
Boolean, char, integer, real, longint, set ( 0..31)
ArrayType = ARRAY length(“,” length) of type – нужно явно указывать длину массива, динамических массивов нет. Нумерация массивов с 0.
RecordType = RECORD [“(BaseType)”] [Fields] end;
PointerType = POINTER to type; Только для записей и массивов. Нельзя преобразовать указатель в число, но есть функция addr, возвращающая int. Число нельзя интерпретировать как указатель.
Система Oberon имеет сборщик мусора.
Процедурная переменная – указатель на функцию.
ProcedureType = PROCEDURE[formal parameters].
“=” | “#” (не равно) | “<” | “<=” | “>” | “>=” | “IN” | “IS”
“+” “-” “OR”
“*” “/” “DIV” “MOD” “&”
If (ch>=”A”) then
elsif (ch>=”0”) then
elsif (ch=22x) then ReadStr.
End.
CASE k of
0:
|1:
|2:
End.
Компилятор оптимизирует, делая goto по таблице, где индексы – ключ k.
Oberon минималистичен – из него нельзя ничего выбросить.
WHILE j>0 DO
j:=j DIV 2;
i:=i+1;
END;
While m>n DO m:=m – n
Elsif n>m DO n:=n-m
REPEAT
UNTIL expression (условие выхода из цикла)
For v:=beg to en BY inc do s end
Beg, en вычисляются только 1 раз.
RETURN м.б. только в конце процедуры, иначе м.б. нарушены принципы структурного программирования.
Существует стандартный модуль System для разработки ОС.
В целях упрощения отказались от обработки исключений.
Component Pascal
ООП + Oberon = Component Pascal
Главная идея уточнений по сравнению с Обероном была в том, чтобы дать проектировщику компонентного каркаса (т.е. интерфейсов модулей, определяющих абстрактные классы для конкретной проблемной области) более полный контроль над ее проектируемыми свойствами в плане безопасности. Введены специальные атрибуты для типов (ABSTRACT, EXTENSIBLE, LIMITED) и методов (ABSTRACT, EMPTY, EXTENSIBLE), позволяя автору программной компоненты (группы модулей) осуществлять контроль в плане того, разрешать или нет модулям-клиентам расширять предлагаемые им типы.
2. Модернизирована несколько устаревшая система основных типов Оберона: теперь набор основных типов Компонентного Паскаля является надмножеством для основных типов языка Java. Основные «рабочие» типы INTEGER, REAL и CHAR соответствуют 32-, 64- (т. н. двойная точность) и 16-(Unicode)-битовым переменным, что позволяет уменьшить разнообразие основных типов, реально используемых в большинстве случаев; использование других типов (LONGINT, SHORTREAL, SHORTCHAR и т. д.) ограничивается специальными приложениями.
3. Добавлены базовые средства для работы с цепочками литер (неявный тип String), что делает Компонентный Паскаль более удобным, чем Паскаль или Оберон, для работы со строками. Цепочки литер представляются массивами литер (ARRAY OF CHAR или ARRAY OF SHORTCHAR), причем значением считается последовательность литер до первого вхождения специальной литеры-ограничителя 0X. Цепочки литер можно сравнивать (подразумевается лексикографическое сравнение) и складывать (конкатенация). Конструкция a := b$ позволяет скопировать в массив литер a цепочку, хранящуюся в массиве литер b (включая литеру-ограничитель 0X), даже если присваивание a := b запрещено (например, из-за разной длины массивов a и b).
13. Имитация модульного программирования в языке C++. Понятие пространства имен.
Имитация модульного программирования в языке C++
В языке С++ очень бедные средства модульного программирования, поэтому для достижения модульности программ, следует придерживаться определенных принципов.
Роль программного интерфейса модуля играет h-файл, а cpp-файл – роль реализации этого модуля.
Внутрь h-файла включаются h-файлы других модулей, необходимые для компиляции интерфейсной части. Внутрь cpp-файла включаются h-файлы других модулей, необходимые для компиляции cpp- и h-файлов интерфейсной части модуля.
Очевидно, что программисту при включении h-файла другого модуля предоставляется выбор: подключить его в h-файле модуля или в cpp-файле. В данном случае предпочтение следует отдавать части реализации модуля (cpp-файл).
При подключении h-файла следует придерживаться следующей схемы: предположим, что наш модуль называется SysModule и состоит из двух частей: SysModule.h и SysModule.cpp.
Рекомендуется следующая схема подключения:
SysModule.h:
#include"Config.h" // наш файл конфигурации подключается первым во всех h-файлах всех наших й проектов
#include"Другой стандартный модуль"
#include"Другой наш модуль"
SysModule.cpp:
#include"Файл предкомпилированных заголовков"
#include"Еще один наш модуль"
#include"Другой стандартный модуль"
#include"SysModule.h" // подключается последним
Поскольку один и тот же h-файл может одновременно включатся в другие h-файлы и несколько раз подключаться при компиляции одного и того же cpp-файла, его следует защищать от повторной компиляции. Для этого в начале любого h-файла вставляются следующие директивы компилятора:
#ifndef __SysModule_h__
#define __SysModule_h__
...
#endif
Таким образом, в том случае, когда файл подключается несколько раз, скомпилируется он только один раз.
Согласно стандарту ISO, любой h- и cpp-файл в С++ должен заканчиваться символом перевода строки.
Предкомпилированные заголовки
Так как содержимое стандартных заголовков не изменяется, то их компиляция при каждой сборке проекта является напрасной тратой времени. Предкомпилированные заголовки помогают решить эту проблему – стандартные файлы компилируются один раз, а затем используется скомпилированный двоичный образ.
Предварительно откомпилированные заголовки – в средах программирования на языках С и С++ – способ ускорить компиляцию программ за счёт предварительной обработки так называемых заголовочных файлов, которые содержат интерфейсы модулей и, согласно нормам данных языков программирования, подключаются к программе путём прямой вставки их текстов в тело основной программы с помощью специальной директивы препроцессора #include. Предкомпилированные заголовки сохраняются на диске в виде файлов во внутреннем формате компилятора и при повторных компиляциях проекта время на их обработку и подключение существенно сокращается.
Впрочем, предварительная компиляция заголовка помогает не всегда:
При изменении любого из предкомпилируемых заголовков перекомпилируется весь набор.
При полной перекомпиляции выигрыш по времени получается, когда один и тот же набор применяется как минимум в двух единицах компиляции.
Поэтому, как правило, в предкомпилируемый набор включают всевозможные библиотечные заголовки, крупные и в то же время редко изменяющиеся.
Принцип действия предкомпилированных заголовков
Для управления предкомпилированными заголовками предназначена директива компилятора #pragma hdrstop. Все заголовочные файлы, включенные до этой директивы, помещаются в один образ, например:
#include <vcl.h> #include <string> #pragma hdrstop
Такая последовательность создаст образ, содержащий скомпилированные vcl.h и string. Этот образ будет использован для другого cpp-файла, если в нем до директивы hdrstop будут включены те же файлы, в том же порядке. Обращу внимание, что важен не только состав, но и порядок следования заголовков – даже если следующий cpp-файл включает те же заголовки, но сначала указан string, а потом vcl.h, то для этого cpp-файла будет создан новый образ.
Сократить затраты на компиляцию стандартных заголовков до минимума можно только в том случае, если скомпилировать один образ, содержащий все стандартные заголовки, необходимые для проекта. Для этого нужно, чтобы:
- ВСЕ cpp-файлы проекта имели одинаковый блок включений до директивы hdrstop; - в этот блок должны входить ВСЕ стандартные заголовочные файлы, необходимые для проекта.
Понятие пространства имен
В больших проектах наблюдается серьезная проблема – конфликт идентификаторов. Она решается с помощью пространства имен.
namespace Sys
{
int var;
void Proc();
}
Внутри пространства имен обращение к определенным внутри переменным и подпрограммам можно осуществлять, используя неполную форму записи:
var = 10;
Proc();
за пределами надо использовать полную форму записи:
Sys::var = 10;
Sys::Proc();
Для того чтобы избежать возможного конфликта идентификаторов, все определения внутри модуля следует помещать в пространство имен. Следует давать небольшой буквенный идентификатор, который будет соответствовать префиксу файла.
Существует возможность открыть пространство имен таким образом, чтобы можно было использовать неполную форму записи. Для этого надо написать строку:
using namespace Sys; (директива)
Но следует отметить, что данная конструкция является причиной многих ошибок, поэтому так писать не стоит.
Существует второй способ открыть пространство имен – это открыть его для конкретного определения:
using Sys::Proc(); (определение)
Но рекомендуется использовать Sys::Proc();
Идентификаторы, объявленные вне пространства имен, относятся к так называемому глобальному пространству имен, доступ к которым осуществляется с помощью оператора ::
::Funk();
Для того чтобы была возможность закрыть доступ к данным и подпрограммам внутри данного пространства существует пространство имен без имени:
namespace
{
...
}
Пространства имен могут быть вложенными:
namespace Sys
{
namespace Local
{
int var;
...
}
...
}
Sys::Local::var = 10;
Замечание! Когда возникает желание объявить переменный тип данных или подпрограмму внутри пространства имен, а реализовать за пределами (или наоборот), следует поступать так:
Классы, функции и переменные, которые являются стандартными компонентами компиляторов С++ , находятся в пространстве имен std. Это относится к заголовочным файлам без .h.
Сделать доступным пространство имен std для программы можно несколькими способами.
• Можно поместить
using namespace std ;
перед определением функции в файле, в результате чего все содержимое пространства имен std будет доступно для каждой функции в файле.
• Можно поместить
using namespace std; в определении функции
• using std::cout;
• std ::cout.
Пространства имен могут находиться на глобальном уровне или внутри других пространств имен, однако они не могут быть помещены в блок. Следовательно, имя, объявленное в пространстве имен, по умолчанию обладает внешним связыванием (если оно не ссылается на константу).
Пространства имен открыты. Это означает, что можно включать новые имена в существующие пространства имен.
Предположим, что одно и то же имя определено как в пространстве имен, так и области объявлений. При попытке применить объявление using, чтобы поместить имя из пространства
имен в область объявлений, оба имени вступят в конфликт, и отобразится сообщение об ошибке. Если с помощью директивы using перенести имя из пространства имен в область
объявлений, локальная версия этого имени перекроет версию из простраства имен.
Недопустимо использование имен из неименованного пространства в любом файле, кроме того,
что содержит объявление пространства имен. Это может служить альтернативой применению статических переменных с внутренним связыванием.
14. Классы в языке C++. Наследование. Конструкторы и деструкторы. Стандартные конструкторы. Создание объектов по значению (на стеке) и по ссылке (в динамической памяти). Операторы new и delete. Размещающий оператор new. Порядок конструирования и разрушения объектов. Вложенные определения классов. «Друзья» класса. Статические члены класса.
Спецификация базового типа выполняет три вещи:
• Определяет, сколько памяти нужно объекту.
• Определяет, как интерпретируются биты памяти.
• Определяет, какие операции, или методы, могут быть применены с использованием этого объекта данных.
Классы в С++
Классы в С++ определяются с помощью одного из ключевых слов: class или struct.
Атрибуты доступа в классах: public, protected, private. Их можно чередовать.
В работе секций protected и private в Delphi и C++ есть различия:
В Delphi классы внутри одного модуля могут обращаться к данным и подпрограммам друг друга без ограничений. А действие секций protected и private распространяется только за пределами данного модуля. В С++ действие этих секций распространяется на любые два класса. Но установленные ограничения можно обойти с помощью специального оператора friend:
Class TTextReader
{
friend class TList;
};
После этого объект класса TList может обращаться к полям из секций private и protected класса TTextReader.
Метод класса может быть реализован по месту или отдельно от класса:
class TTextReader
{
public:
TTextReader();
~TTextReader() { ... } // по месту
};
TTextReader::TTextReader() // отдельно от класса – квалифицированное имя
{
...
}
Если класс описан в интерфейсной части модуля, его методы рекомендуется реализовывать отдельно от класса в cpp-файле, иначе при компиляции получаются огромные объектные модули. В том случае, когда некоторый класс надо сделать inline-методом, следует писать так:
class TTextReader
{
public:
TTextReader();
~TTextReader();
int ItemCount();
};
inline int TTextReader::ItemCount()
{
...
}
Любая функция внутри определения класса автоматически становится встроенной.
При обращении к inline-методу идет обращение к полю. Метод д.б. перенесен в h-файл. Иначе компилятор будет делать вызов метода. На этапе компоновки получим ошибку линкера «нет метода».
В С++ объекты могут агрегироваться по ссылке и по значению (агрегирование по ссылке похоже на агрегирование в Delphi).
Агрегирование по значению:
class TDelimitedReader
{
public:
...
private:
std::string m_FileName;
};
Агрегированные по значению объекты конструируются автоматически в порядке объявления после вызова конструктора базового класса (если не указан другой способ инициализации).
Стандартный способ инициализации можно переопределить до открывающей фигурной
скобки конструктора:
TTextReader::TDelimitedReader() : TTextReader(), m_FileName("c:/myfile.txt")
{
...
}
Следует отметить, что данная запись отличается от следующей записи:
TDelimitedReader::TDelimitedReader() : TTextReader()
{
m_FileName = "c:/myfile.txt";
}
Во втором случае строка вначале создается пустой, а в теле конструктора переприсваивается.
Объекты, агрегированные по ссылке, нужно создавать вручную с помощью оператора new, а удалять – с помощью оператора delete:
class TDelimitedReader : publicTTextReader
{
...
private:
std::string m_FileName;
TItems *m_Items;
};
TDelimitedReader::TDelimitedReader() : TTextReader(), m_FileName("c:/myfile.txt")
{
m_Items = new TItems;
}
TDelimitedReader::~TDelimitedReader()
{
delete m_Items;
}
Правило конструирования агрегированных объектов:
объекты, агрегированные по значению и константные ссылки инициализируются до тела конструктора.
объекты, агрегированные по ссылке, инициализируются в теле конструктора.
Агрегация – размещение данных в виде полей внутри других данных.
Наследование
Наследование класса выполняется следующим образом:
сlass TDelimitedReader: public TTextReader
{
...
};
При наследовании указываются атрибуты доступа к элементам базового класса (public, protected, private). Для того чтобы понять смысл атрибута доступа к базовому классу, базовый класс следует рассматривать, как поле производного класса.
Private-наследование: в производном классе не видно то, что в базовом классе объявлено как private.
Protected-наследование: в производном классе видим public & protected.