Простые типы 2 страница

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

Рассмотрим пример создания рекурсивной подпрограммы для вычисления факториала. Математический факториал определяется так:

n!=1*2*3*…*n

0!=1

n!=n*(n-1)!

Подпрограмма на С по использованию рекурсивной функции:

int factorial (int n)

{

if (n==0) return 1;

else return n*factorial(n-1);

}

Свойства необходимые для поддержки рекурсии:

- Компилятор должен выдавать чистый код. Так как при каждом обращении к функции (factorial) используется одна и та же последовательность машинных команд, поэтому код не должен изменять сам себя.

- Выделение во время выполнения программы произвольного числа ячеек для параметров и локальных переменных.

Для вычисления factorial(4) выделяется пять ячеек памяти. Сперва для 4, затем 3, 2, 1 и 0! Требование выделения памяти поддерживается стековой архитектурой.

Замечание: Функцию factorial можно легко написать используя итерационный цикл. Рекурсию используют тогда, когда итерационное решение трудно запрограммировать.

8.4. Стековая архитектура.

Стек- это структура данных, которая принимает и выдает данные в порядке LIFO-Lost-In, First-Out (последний пришел, первым вышел). Стек нужен для хранения локальных переменных и параметров процедуры. Память для них автоматически выделяется при вызове процедуры и освобождается после завершения ее работы.

Стек может быть реализован с помощью массива или списка. Преимущество списка в том, что он ограничен только общим объемом доступной памяти. Преимущество массива в их использовании при реализации языков программирования. В состав стека помимо массива или списка входит еще один элемент- указатель вершины стека (top-of-stark pointer). Это индекс первой доступной пустой позиции в стеке. На первую позицию в стеке указывает переменная top. В стеке допустимы две операции:

1. push - процедура, получающая элемент как параметр, который она помещает в вершину стека и увеличивает указатель вершины top;

2. pop - функция, которая возвращает верхний элемент стека, уменьшая top и указывая, что эта позиция стала новой пустой позицией.

Реализация стека:


8.4.1. Записи активизации.

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

Вид записей активации для вложенных процедур:

procedure Main is

G: Integer;

procedure Proc_1 (P1: Integer) is

L1: Integer;

procedure Proc_2 (P2: Integer) is

L2: Integer;

begin

L2:=L1+ G+ P2;

End Proc_2;

begin Proc_1

Proc_2(P1);

end Proc_1;

begin Main

Proc_1(g);

end Main;

Вызов процедуры реализуется следующим образом (по рисунку):

1. В стек помещаются фактические параметры. К ним можно обращаться по смещению от начала записи активации.

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

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

4. Выполняется переход к коду процедуры.

После завершения процедуры перечисленные шаги выполняются в обратном порядке:

1. Индекс вершины стека уменьшается на величину объема памяти, выделенной для локальных переменных.

2. Адрес возврата извлекается из стека и используется для восстановления указателя команд.

3. Индекс вершины стека уменьшается на величину объема памяти, выделенной для фактических параметров.

8.4.2. Реализация стека на процессоре 8086.

Рассмотрим пример программы:

procedure Main is

clobal: Integer;

procedure Proc (Parm: in Integer) is

Local1, Local2: Integer;

begin

Local2:= Clobal +Parm +Local1;

end Proc;

begin

Proc(15);

end Main;

Процессор 8086 имеет встроенные команды push и pop, в которых стек растет от старших адресов к младшим. Для стековых операций выделены два регистра:

sp - указывает на верхний элемент в стеке;

bp - указатель дна, идентифицирует начало записи активации.

Реализация вызова процедуры:

mov ax, #15 - Загрузить значение параметра.

push ax - Сохранить параметр в стеке.

call Proc - Вызвать процедуру.

Стек после выполнения этих команд

Реализация входа в процедуру:

push bp - Сохранить старый динамический указатель.

mov bp, sp - Установить новый динамический указатель.

sub sp, #4 - Выделить место для локальных переменных.

Стек после входа в процедуру:

Реализация тела процедуры:

mov ax, ds:[38] - Загрузить переменную Global

add ax, [bp+06] - Прибавить параметр Parm

(198+06)=204- значение ячейки 204.

add ax, [bp-02] - Прибавить переменную Local1, которая

находится в ячейки 198-02=196.

mov ax, [bp] - Сохранить в переменной Local2.

При выходе из процедуры должны быть ликвидированы все изменения, сделанные при входе в процедуру:

mov sp, bp - Очистить все локальные переменные.

pop bp - Восстановить старый динамический

указатель.

ret 2 - Вернуться и освободить память

параметров.

8.5. Распределение памяти.

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

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

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

  1. Константы - небольшие константы, такие как 3 и 'у', которые содержатся внутри команды. Для больших констант, например с плавающей точкой, память должна выделяться особо.
  2. Стек - память, используемая в основном для записей активации, которые содержат параметры, переменные и ссылки. Она также используется для временных переменных при вычислении выражений.
  3. Статические данные - память для переменных, объявленных в главной программе и подпрограммах: в Ada - данные, объявленные внутри библиотечных параметров; в С - данные, объявленные внутри файла или как статические в блоке.
  4. Динамическая область памяти – область памяти для данных, которые динамически выделяются командой malloc в С и new в Ada и C++.

Программа обычно помещается в отдельную непрерывную область, разделенную на участки:

Рис. Распределение памяти.

Код и константы определяются во время компиляции и уже не меняются. Поэтому они занимают одну часть памяти.

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

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

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

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


9. Использование VISUAL C++ 6.0

9.1 Обработка сообщений
Если и существует некоторая особенность, отличающая программирование в Windows от других областей программирования, то это сообщения. Большинство DOS-программ, например, основано на отслеживании возможных источников поступления информации, таких как клавиатура и мышь, ожидая ввода от них. Программа, которая не следит за мышью, не сможет реагировать на поступающие от нее сигналы. Что же касается Windows-программ, то там все происходит с точностью да наоборот: программа управляется сообщениями. Сообщения являются тем средством, с помощью которого операционная система может дать знать приложению, что что-то произошло, например пользователь нажал клавишу на клавиатуре или щелкнул кнопкой мыши, или передвинул мышь, или подготовил принтер к выводу информации. Окно, а каждый информационный элемент на экране есть своего рода окно, также может посылать сообщения другому окну и, как правило, большинство окон реагирует на полученное сообщение тем, что пересылает его дальше, третьему окну, слегка видоизменив. Значительную помощь в организации работы с сообщениями оказывает MFC, скрывая от программиста многие подробности процесса, но грамотный разработчик всегда должен представлять себе, что же происходит там, под ковром.
Хотя операционная система и использует целые числа для идентификации событий, в тексте программы мы будем иметь дело с символьными идентификаторами. Огромное количество директив #define связывает символьные идентификаторы с соответствующими числами и позволяет программистам в разговоре между собой в кругу посвященных манипулировать словечками вроде WM_PAINT и WM_SIZE. Префикс WM означает Window Message (сообщение Windows). Фрагмент перечня сообщений представлен в листинге 1.1.
Листинг 1.1. Фрагмент файла winuser. h — определение кодов сообщений
#define WM_SETFOCUS 0x0007
#define WM_KILLFOCUS 0x0008,
#define WM_ENABIE OxOOOA
#define WM_SETREDRAW OxOOOB
#define WM_SETTEXT OxOOOC
#define WM_GETTEXT OxOOOD
#define WM_GETTEXTLENGTH OxOOOE
#define WM_PAINT OxOOOF

#define WM_CLOSE 0x0010
#define WM_QUERYENDSESSION 0x0011

#define WM_QUIT 0x0012
#define WM_QUERYOPEN 0x0013
#define WM_ERASEBKGND 0x0014
#define WM_SYSCOLORCHANGE 0x0015
#define WM_ENDSESS I ON 0x0016

Сообщению известно, для какого окна оно предназначено. Оно может иметь до двух параметров. Часто в эти два параметра упаковывается несколько совершенно различных величин, но это уже другое дело.
Обработка разных сообщений выполняется разными компонентами операционной системы и приложения. Например, когда пользователь передвигает мышь по полю окна, формируется сообщение WM_MOUSEMOVE, которое передается окну, а окно, в свою очередь, передает это сообщение операционной системе. И уже последняя перерисовывает указатель мыши в новом месте. Когда пользователь щелкает левой кнопкой мыши на экранной кнопке, кнопка, которая также есть особый вид окна, получает сообщение WM_LBUTTONDOWN. В процессе обработки этого сообщения кнопка часто формирует новое сообщение для окна, в котором она находится, причем это сообщение гласит: "Ой, на мне щелкнули!".
Библиотека MFC позволяет программистам в подавляющем большинстве случаев полностью отстраниться от сообщений нижнего уровня, таких как WM_MOUSEMOVE и WM_LBUTTONOOWN. Программист может полностью сосредоточиться на сообщениях более высокого уровня, которые гласят что-нибудь вроде "Выбран третий элемент такого-то списка" или "Произошел щелчок на кнопке Move". Поступают такого рода сообщения в те программы, которые пишет программист, и в компоненты операционной системы точно так же, как и сообщения нижнего уровня. Единственная разница в том, что MFC берет на себя значительную часть работы по обработке сообщений низкого уровня и позволяет заметно облегчить распределение сообщений между разными классами объектов, на уровне которых и будет производиться их обработка. В программах на языке С, которые были созданы по старой технологии, такое объявление выполнялось на достаточно высоком уровне взаимодействия между собственно языком и системой Windows. При этом в дело вступали различные средства объектно-ориентированного программирования, которые позволяли в максимальной степени скрывать детали выполняемых операций внутри объектов.

9.2 Циклы обработки сообщений
Сердцем любой Windows-программы является цикл обработки сообщений (Message Loop), который практически всегда находится в функции WinMain(). Эта функция в Windows-приложениях играет ту же роль, что и функция Mаin() в DOS-приложениях,— ее вызывает операционная система сразу же после загрузки приложения в память. К большому облегчению программистов, теперь они могут не отвлекаться на набивку текста WinMain (), поскольку это сделает AppWizard. Но это не значит, что сама функция исчезла. Текст типичной функции WinMain() представлен в листинге 3 2.
Листинг 1.2. Типичная функция WinMain()
int АРIENTRY WinMain(HINSTANCE h Instance,
HINSTANCE hPrevlnstance,
LPSTR IpCmdLine,
int nCmdShow)

{

MSG msg;
if(! InitAppLication(hlnstance))
return (FALSE);
if(! Initlnstance(hlnstance, nCmdShow))
return (FALSE);
while(GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);

}

return (msg. wParam);

}

В С-программах для Windows, похожих на эту, функция InitAppLication() вызывает RegisterWindow(), a InitInstance() — CreateWindow(). Затем наступает очередь цикла обработки сообщений. Он представляет собой типичную циклическую конструкцию С на базе оператора while, внутри которой вызывается функция GetMessage() Эта функция API заполняет msg кодом сообщения, которое операционная система распределила для этого приложения, и почти всегда возвращает TRUE. Таким образом, цикл повторяется снова и снова до тех пор, пока работает приложение. Единственный вариант, при котором GetMessage()возвращает FALSE, — получение сообщения WM_QU IT.
При работе с сообщениями, поступающими с клавиатуры, некоторую часть предварительной обработки берет на себя функция API TranslateMessage(). Ее назначение состоит в следующем. Прикладной части программы нет дела до сообщений наподобие "Нажата клавиша <А>" и "Отпущена клавиша <А>". Прикладную часть, в конце концов, интересует только то, какую литеру (символ) ввел пользователь, т.е. ее вполне удовлетворит сообщение "Введен символ А". Вот это преобразование — нескольких сообщений о деталях процесса в одно сообщение о его сути— и выполняет функция TranslateMessage(). Она перехватывает сообщения WM_KEYDOWN и WM_KEYUP и вместо них посылает сообщение WM_CHAR. Конечно, если пользоваться библиотекой MFC, то такие мелочи, как ввод символа А, проходят, как правило, мимо вас. Пользователь вводит текст в текстовое поле или в другой элемент управления, и забота программиста — извлечь введенный текст из этого объекта после того, как пользователь щелкнет на ОК. Как был организован прием символов с клавиатуры, теперь уже не наше дело. Таким образом, на функцию
TranslateMessage() можно не обращать особого внимания.
Функция API DispatchMessage() вызывает, в свою очередь, функцию WndProc() того окна, для которого предназначено сообщение. Типичная функция WndProc() в С-программе для Windows представляет собой огромный оператор switch с отдельными case для каждого сообщения, которое приложение намеревается самостоятельно обрабатывать. Текст ее приведен в листинге 1.3.
Листинг 1.3. Типичная функция WndProc()
LONG АРIENTRY MainWndProc(HWND hwnd, // Дескриптор окна.

UINTmsg, //Тип сообщения.

UINTwParam, //Дополнительная информация.

LONG IParam) //Дополнительная информация.

{ switch(msg){

case WM_MOUSEMOVE: {

// Обработка перемещения мыши.

break;

case WM_LBUTTONDOWN: {

// Обработка щелчка левой кнопкой мыши.

break;

case WM_RBUTTONDOWN: {

// Обработка щелчка правой кнопкой мыши.

break;

case WM_PAINT:

// Перерисовать окно.

break;

case WM_DESTROY //Сообщение: окно будет уничтожено. PostQuitMessage(0);

return 0;

break;

default:

return (DefWindowProc(hwnd, msg, wParam, IParam));

}

return (0);

}

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

9.3 Карты сообщений
Использование карты сообщений (Message maps) лежит в основе подхода, который реализуется в MFC для программирования Windows-приложений. Суть его состоит в том, что от разработчика требуется только написать функции обработки сообщений и включить в свой класс карту сообщений, которая фактически скажет: "Я буду обрабатывать такое-то сообщение". После этого главная программа будет отвечать за то, чтобы сообщение было передано именно той функции, которая будет его обрабатывать. В результате исключается необходимость разрабатывать функцию WinMamO, которая должна была бы передавать сообщения другой функции — WndProc() (ее также нужно было бы разработать), а последняя должна была бы анализировать сообщение и вызывать соответствующую функцию обработки.
Если вам приходилось работать с Visual Basic, то вы наверняка знакомы с такими процедурами обработки событий (event procedure), как, например, "щелчок мышью". Функция обработки сообщения в программе на C++ играет ту же роль, что и процедура обработки события. Карта сообщений — это способ связать событие с его обработчиком.
Карта сообщений состоит из двух частей: одна — в файле заголовка для класса.h, а другая — в соответствующем файле реализации.срр. Они, как правило, формируются мастерами, хотя в некоторых случаях вы можете сделать это (или частично отредактировать их) и самостоятельно. В листинге 1.4 представлена часть текста файла заголовка одного из классов простого приложения ShowString.
Листинг 1.4. Карта сообщений из файла ShowString. h
//{{AFX_MSG(CShowStringApp)
afx_msg void OnAppAboutO;

// ВНИМАНИЕ!! Здесь ClassWizard будет добавлять и

// удалять функции-члены.

// НЕ РЕДАКТИРУЙТЕ текст в этих блоках1 //}}AFX_MSG
DECLARE_MESSAGE_MAP()
Здесь объявляется функция OnAppAbout(). Специальным образом оформленный комментарий позволяет ClassWizard определить, какие именно сообщения перехватываются этим классом. DECLARE_MESSAGE_MAP — это макрос, расширяемый препроцессором компилятора Visual C++, в котором объявляются переменные и функции, принимающие участие в этом фокусе с перехватом сообщений.
Карта сообщений в файле. срр, как показано в листинге 1.5, также достаточно проста.
Листинг 1.5. Карта сообщений из файла ShowString.cpp
BEGIN_MESSAGE_MAP(CShowStringApp, CWinApp)

//<{AFX_MSG_MAP (CShowStringApp)

ON_COMMAND(ID_APP_ABOUT, OnAppAbout)

//}}AFX_MSG_MAP

// Стандартные команды для файловых документов

ON_COMMAND (ID_FILE_NEW, CWinApp::OnFileNew)

ON_COMMAND (ID_FILE_OPEN, CwinApp::OnFileOpen)

// Стандартные команды настройки принтера

ON_COMMAND(ID_FILE_PRINT_SETUP,CWinApp::OnFilePintSetup)

END_MESSAGE_MAP()

9.3.1. Макросы карты сообщений
Макросы BEGIN_MESSAGE_MAP и END_MESSAGE_MAP, так же как DECLARE_MESSAGE_MAP в файле заголовка, объявляют члены (переменные и функции), которые программа должна использовать для того, чтобы разобраться в картах всех объектов системы. Существует довольно большой набор макросов, используемых для работы с картой сообщений. Некоторые из них перечислены ниже.

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

• BEG I N_MESSAGE_MAP. Отмечает начало карты сообщений в тексте программы.
• END_MESSAGE_MAP. Отмечает конец карты сообщений в тексте программы.

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

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

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

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

• ON_MESSAGE. Используется для того, чтобы перенаправить обработку некоторого сообщения, введенного Программистом, функции-члену класса.

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

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

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

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

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

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

• ON_NOTIFY_EX_RANGE. Используется аналогично макросу ON_NOTIFY_EX, но применяется для группы элементов, извещения от которых должны обрабатываться одной и той же функцией-членом класса. Элементы управления, которые передают сообщения такого формата, являются дочерними окнами по отношению к тому окну, которое эти сообщения перехватывает.
В дополнение к перечисленным существует еще около ста макросов, по одному на каждое стандартное сообщение, которые направляют соответствующее сообщение функции-члену. Например, ON_CREATE направляет сообщение WM_CREATE функции On_Create(). При пользовании такими макросами нельзя изменять имена функций. Эти макросы, как правило, включаются в карту сообщений самим ClassWizard.

9.3.2. Что происходит с картой сообщений
Карты сообщений, приведенные в листингах 1.4 и 1.5, созданы для класса ShowStringApp приложения ShowStrmg. Этот класс отвечает за выполнение задач достаточно высокого уровня, например открытие нового файла или отображение окна About. Компоненты, которые добавлены в карту сообщений файла заголовка, могут быть истолкованы следующим образом: "Существует функция OnAppAbout(), которая не имеет параметров". Компонент, который добавлен в собственно текст программы (файл. срр), означает: "Когда придет сообщение от команды ID_APP_ABOUT, вызовите OnAppAbout()". Ничего неожиданного в том, что функция-член OnAppAbout() выведет на экран окно About этого приложения, естественно, нет.
Но что же в действительности происходит при этом с картой сообщений? Каждое приложение имеет объект, который является наследником класса CWinApp, и имеет функцию-член Run(). Эта функция обращается к CWinThread::Run(), которая значительно длиннее, чем продемонстрированная вам ранее функция WinMain(), но имеет точно такой же цикл обработки сообщений— вызов GetMessage(), вызов TranslateMessage() и вызов DispatchMessage(). Почти все объекты-окна используют тот же самый класс окна, характерный для прежней технологии программирования, и ту же самую функцию WndProc(), но теперь названную AfxWndProc(). Функция WndProc(), как вы уже видели, знает дескриптор окна hWnd, для которого предназначено сообщение. Библиотека MFC, в свою очередь, содержит нечто, называемое картой дескрипторов (handle map), — таблицу дескрипторов окон и указателей объектов. Таким образом, главная программа может, используя всю эту информацию, найти указатель на объект cWnd *. Далее она вызывает WindowProc()— виртуальную функцию этого объекта. Кнопки или окна представления, естественно, имеют разные реализации этой функции, но волшебные свойства полиморфизма приводят к тому, что вызывается именно та реализация, которая нужна.

Полиморфизм
Виртуальные функции и полиморфизм — это базовые понятия концепции объектно-ориентированного программирования. Они должны быть известны любому программисту, работающему на языке C++, особенно если он пользуется библиотеками такого класса, как MFC. С ними встречаешься всякий раз при использовании указателей на объекты, особенно если эти объекты принадлежат классам, производным от других классов. В качестве примера рассмотрим некоторый класс CDerived, производный от класса CBase. Пусть этот класс имеет функцию-член Function(), которая объявлена в базовом классе и перегружена в производном классе. Теперь в нашем распоряжении две функции: полное имя одной — CBase::Function(), а другой — CDerived::Function(). Если в тексте вашей программы объявлен указатель на объект базового класса, а вы присваиваете ему адрес объекта производного класса, то обращение к функции может иметь следующий вид:

CDerived denvedobject;
CBase* basepointer;
basepointer = &denvedobject;
basepointer->Function(),
В этом случае будет вызвана функция CBase::Function(). Но может оказаться, что это совсем не то, что вам нужно. Хотя и используется указатель на объект класса CBase, в действительности вам нужно вызвать функцию класса CDerived. Чтобы указать на такой способ обращения к функции Function(), она объявляется в базовом классе как виртуальная функция. Это можно интерпретировать, как указание компилятору перегрузить данную функцию при первой же возможности в производном классе.
Поскольку функция Function()объявлена в базовом классе CBase как виртуальная, приведенный выше фрагмент программы действительно вызовет CDerived::Function(), как того и добивался разработчик. Это и есть полиморфизм на практике. Подобные случаи вы сможете неоднократно встретить при работе с классами, объявленными в MFC. Например, вы используете для обращения к объекту производного класса CButton или CView (или любого аналогичного) указатель, объявленный как CWnd*. Затем, если вызывается функция типа WindowProc(), компилятор сформирует обращение к функции-члену производного класса CButton WindowProc().
Функция WindowProc() вызывает OnWndMsg() — функцию C++, которая собственно и обрабатывает сообщения. Во-первых, она проверяет, что же это было — сообщение, команда или код извещения. Предположим, поступило сообщение. Тогда функция просматривает карту сообщений для своего класса, используя члены класса (переменные и функции), которые были установлены макросами BEGIN_MESSAGE_MAP, END_MESSAGE_MAP и DECLARE_MESSAGE_MAP. Помимо всего прочего, эти макросы организуют доступ к компонентам карты сообщений базового класса посредством функций, которые анализируют карту сообщений производного класса. Это означает, что если класс является производным от CView, но не перехватывает сообщений, которые обычно перехватываются базовым классом, то сообщение будет перехвачено функцией класса CView, которая унаследована производным классом. Этот механизм наследования в карте сообщений работает параллельно с механизмом наследования C++, но независимо от него, и, таким образом, позволяет избежать многих сложностей, связанных с использованием виртуальных функций.
Подведем черту! Вы включаете в программу некоторый компонент карты сообщений и, когда такое сообщение возникает, функции, вызванные скрытым от вас циклом обработки сообщений, решают на основании этой таблицы, какой из объектов и какая из функций-членов этого объекта будет обрабатывать сообщение. Вот что в действительности происходит там, за кулисами.

9.3.3. Сообщения, которые перехватываются функциями MFC
Другое громадное преимущество MFC состоит в том, что в этой библиотеке уже имеются готовые классы, которые перехватывают и обрабатывают большинство распространенных сообщений, причем делают это безо всяких усилий со стороны разработчика программы. Например, вам не нужно заботиться об обработке таких сообщений, как вызов команды File Save As. Классы MFC самостоятельно "отловят" это сообщение, выведут на экран диалоговое окно для ввода нового имени файла, обработают все манипуляции пользователя в этом окне, короче говоря, сделают для вас всю черновую работу и в конце вызовут разработанную уже вами функцию Serialize(), которая и запишет данные в файл. AppWizard, как правило, формирует пустую функцию Serialize(), в которую разработчик должен вставить необходимый текст. Таким образом, программисту необходимо вставлять в карту сообщений компоненты только для тех случаев, когда обработка некоторого сообщения в данном приложении отличается от общепринятой методики.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: