Все права на текст принадлежат автору: Александр Вячеславович Фролов, Григорий Вячеславович Фролов.
Это короткий фрагмент для ознакомления с книгой.
Microsoft Visual C++ и MFC. Программирование для Windows 95 и Windows NTАлександр Вячеславович Фролов
Григорий Вячеславович Фролов

Фролов А.В., Фролов Г.В. Библиотека системного программиста Том 24 Microsoft Visual C++ и MFC. Программирование для Windows 95 и Windows NT

Введение

В предыдущих томах серии “Библиотеки системного программиста” мы ориентировались в первую очередь на язык программирования Си. Даже если некоторые программы были написаны на Си++, то богатые возможности этого языка практически не использовались.

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

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

Современные интегрированные средства разработки приложений Windows позволяют автоматизировать процесс создания приложения. Для этого используются генераторы приложений. Вы отвечаете на вопросы генератора приложений и определяете свойства приложения – поддерживает ли оно многооконный режим, технологию OLE, трехмерные органы управления, справочную систему. Генератор приложений создаст приложение, отвечающее вашим требованиям и предоставит вам его исходные тексты. Пользуясь ими как шаблоном, вы сможете быстро разрабатывать свои приложения.

Подобные средства автоматизированного создания приложений включены в компилятор Microsoft Visual C++ и называются MFC AppWizard – волшебник. Действительно, то что делает MFC AppWizard сродни волшебству. Заполнив несколько диалоговых панелей, можно указать характеристики приложения и получить его исходные тексты с обширными комментариями. MFC AppWizard позволяет создавать однооконные и многооконные приложения, а также приложения, не имеющих главного окна – вместо него используется диалоговая панель. Вы можете включить поддержку технологии OLE, баз данных, справочной системы.

Возможности MFC AppWizard позволяют всего за несколько минут создать собственный многооконный редактор текста с возможностью сервера и клиента OLE. При этом вы не напишите ни единой строчки текста, а исходный текст приложения, созданный MFC AppWizard, можно сразу оттранслировать и получить выполнимый модуль приложения, полностью готовый к использованию.

Конечно волшебство MFC AppWizard не всесильно. Прикладную часть приложения вам придется разрабатывать самостоятельно. Исходный текст приложения, созданный MFC AppWizard станет только основой, к которой надо подключить остальное. Но не стоит разочаровываться – работающий шаблон приложения это уже половина всей работы. Исходные тексты приложений автоматически полученных от MFC AppWizard могут составлять сотни строк текста. Набор его вручную был бы очень утомителен.

Надо отметить, что MFC AppWizard создает исходные тексты приложений только с использованием библиотеки классов MFC. Поэтому только изучив MFC вы сможете пользоваться средствами автоматизированной разработки и создавать свои приложения в кратчайшие сроки.

Microsoft Visual C++

Существует несколько версий компилятора Microsoft Visual C++. Наибольшее распространение получили версии 1.51, 2.0 и 2.2. В начале 1996 года появилась новая версия Visual C++ 4.0.

Microsoft Visual C++ версии 1.51 содержит только 16-разрядный компилятор. Он позволяет разрабатывать программы для операционной системы MS-DOS и Windows 3.1. Эта версия компилятора не позволяет создавать приложения, предназначенные специально для 32-х разрядных операционных систем Windows NT и Windows 95.

Версия 2.0 содержит 32-разрядный компилятор. С его помощью можно создавать приложения для Windows NT и Windows 95. Сама среда Microsoft Visual C++ версии 2.0 может работать только в 32-разрядной операционной системе Windows NT или Windows 95. Как это ни печально, но Visual C++ версии 2.0 не позволит вам создать приложения для операционных систем MS-DOS и Windows 3.1. Для этого предлагается воспользоваться предыдущей версией компилятора.

На этом различия между Visual C++ версий 1.51 и 2.0 заканчиваются. В Visual C++ версии 1.51 используется 16-и разрядная библиотека классов MFC версии 2.5, а Visual C++ 2.0 поставляется с 32-разрядной библиотекой MFC версии 3.0.

При переходе к 32-разрядным приложениям фирма Microsoft отказалась поддерживать элементы управления VBX. Вместо них MFC 3.0 позволяет работать с элементами управления OLE. Новый стандарт на элементы управления получил название OCX (OLE Custom Controls).

Фирма Microsoft приложила большие усилия, чтобы организовать совместимость своего компилятора с различными архитектурами компьютеров. Специальные версии Visual C++ работают на компьютерах с MIPS архитектурой, на компьютерах с процессорами Alpha, Motorola PowerPC. Это позволяет легко переносить разработанные приложения с одной аппаратной платформы на другую.

Популярность библиотеки классов MFC настолько высока, что такие фирмы, как Watcom и Symantec, известные своими компиляторами Си и Си++, приобрели у Microsoft лицензии на эту библиотеку. Практически единственной широко известной в России фирмой, которая поставляет компиляторы Си++ с собственной библиотекой классов является Borland. Компиляторы Borland C++ включают библиотеку классов OWL (Object Windows Library). Эта библиотека совершенно не совместима с библиотекой MFC, поэтому программы, построенные с ее использованием, нельзя транслировать компилятором Visual C++. Компилятор Borland C++ версии 5.0 позволяет транслировать приложения, созданные с использованием библиотеки классов MFC.

Microsoft Visual C++ версия 4.0

В конце 1995 года появилась новая версия Microsoft Visual C++ – 4.0. Этот компилятор интегрирован в среду Microsoft Developer Studio , в состав которого могут входить другие программные продукты Microsoft.

Интегрированная среда разработчика объединяет одной оболочкой Microsoft Visual C++ версии 4.0, Microsoft Visual Test, Microsoft Fortran PowerStation, Microsoft Development Library и Microsoft Visual SourceSafe

Для программистов, работающих в среде Visual C++, наиболее интересна возможность интегрирования с базой данных Microsoft Development Library. После того как вы установите в компьютере Microsoft Development Library, вы сможете осуществлять поиск интересующей вас информации не только в справочной базе Visual C++, но и в базе данных Microsoft Development Library. При этом вам не придется переключаться на другое приложение, достаточно выбрать справочную систему в панели Info Viewer.

Следующие версии Microsoft Visual C++

Весной 1996 года вышла новая версия Microsoft Visual C++ 4.1. В состав этой версии продукта включены дополнительные средства для разработки приложений, поддерживающих глобальную сеть Internet, язык моделирования виртуальной реальности (Virtual Reality Modeling Language – VRML), игровое SDK (Windows 95 Games SDK ) и большое количество OCX объектов, которые вы можете включать в свои приложения.

Поддержка сети Internet
Последние версии Microsoft Internet Information Server, используемого в качестве серверов WWW и FTP, имеют программный интерфейс (Internet Server application programming interface – ISAPI ). Используя этот интерфейс, вы можете разрабатывать собственные модули, взаимодействующие с сервером.

Чтобы облегчить программистам создание таких модулей, Microsoft Visual C++ версии 4.1 содержит “волшебник” ISAPI Extention Wizard, работающий на тех же принципах, что и MFC AppWizard. ISAPI Extention Wizard использует новые классы библиотеки MFC – CHttpServer , CHttpFilter, CHttpServerContext, CHttpFilterContext и CHtmlStream.

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

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

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

С появлением операционной системы Windows 95 ситуация начала изменяться к лучшему. В Windows 95 встроен специальный набор функций, обеспечивающий приложениям высокоэффективный доступ к видеоадаптеру. Вышли первые игры для Windows 95. Так фирма idSoftware выпустила специальную версию популярной игры Doom 2, предназначенную для работы исключительно в среде Windows 95.

Для создания программ, поддерживающих этот набор функций, необходимо использовать специальной средство разработки Games SDK. Ранее оно поставлялось отдельно, а начиная с версии 4.1 компилятора Microsoft Visual C++, вошло в состав этого пакета.

Набор OCX объектов
По сравнению с Microsoft Visual C++ версии 4.0 в версии 4.1 значительно расширен набор органов управления OLE (OCX). В него Microsoft включила органы управления OCX, разработанные другими фирмами.

Как связаться с авторами

Авторы имеют почтовый адрес в сети GlasNet. Все свои замечания и предложения по содержанию книг серий "Библиотека системного программиста", а также "Персональный компьютер – шаг за шагом" вы можете присылать нам по следующему адресу:

frolov@glas.apc.org

Наш почтовый адрес доступен не только пользователям сети GlasNet. Абоненты других компьютерных сетей также могут передавать нам сообщения. Ниже мы приводим наш адрес в различных сетях:

Глобальная сеть Наш адрес
CompuServe >internet:frolov@glas.apc.org
GlasNet frolov@glas.apc.org
Internet frolov@glas.apc.org
Relcom frolov@glas.apc.org
UUCP uunet!cdp!glas!frolov
Вы также можете присылать свои пожелания почтой по адресу:

Издательский отдел АО "ДИАЛОГ-МИФИ".

Индекс 115409, город Москва, улица Москворечье, дом 31, корпус 2.

Приносим свои извинения за то, что не можем ответить на каждое письмо. Мы также не занимаемся рассылкой дискет и исходных текстов к нашим книгам. По этому вопросу обращайтесь непосредственно в издательство “Диалог-МИФИ”.

Благодарности

Авторы выражают благодарность Фроловой Ольге Викторовне, Кустову Виктору. Мы также благодарим всех сотрудников издательского отдела АО "ДИАЛОГ-МИФИ": Голубева Олега Александровича, Дмитриеву Наталью, Виноградову Елену, Кузьминову Оксану.

1. Немного о C++

Несмотря на все многообразие средств, предоставляемых Си++, совершенно необязательно использовать их все сразу. Первым шагом при переходе от Си к Си++ может стать изменение расширений имен исходных файлов ваших программ. Вместо традиционного расширения C в языке Си++ принято использовать расширение CPP. Теперь ваша программа будет транслироваться, как программа, написанная на языке Си++.

Затем вы можете использовать все новые и новые особенности Си++, постепенно отходя от стандартного Си к Си++. На момент написания книги окончательный стандарт Си++ еще не был разработан. Компиляторы различных фирм, например Microsoft и Borland имеют различия в реализации Си++. Наша книга ориентирована в первую очередь на компиляторы Microsoft Visual C++ версий 1.5, 2.0, 4.0 и 4.1.

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

Ввод/вывод

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

Если с левой стороны от оператора << расположен символ cout, то этот оператор осуществляет вывод на экран информации, указанной справа от оператора. Форма, в которой выполняется вывод на экран, зависит от типа выводимого значения. Используя оператор <<, вы можете отображать на экране текстовые строки, а также значения переменных различных типов. В качестве левого параметра оператора << можно использовать не только cout, но также результат работы предыдущего оператора <<. Это позволяет строить цепочки из операторов <<. Чтобы перейти к отображению следующей строки, вы можете передать cout значение \n.

Так, например, следующий фрагмент кода отображает на экране значения переменных iInt, cChar и szString с соответствующими комментариями:

cout << “Значение переменной iInt = ”;

cout << iInt;

cout << “\n”;


cout << “Значение переменной cChar = ” << cChar << “\n”;

cout << “Строка szString = ” << szString << “\n”;

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

int iNum;


cout << "Введите целочисленное значение:";

cin >> iNum;

Чтобы воспользоваться возможностями потокового ввода/вывода, необходимо включить в программу файл iostream.h.

Забегая вперед, скажем, что символы inp и outp, которые иногда называют потоками, представляют собой объекты специального класса, предназначенного для ввода и вывода информации. Операторы << и >> переопределены в этом классе и выполняют новые функции. О переопределении операторов вы можете прочитать в разделе “Перегрузка операторов”.

Константы

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

Ключевое слово const указывают перед объявлением переменной. Такая переменная не может быть модифицирована. Попытки изменить ее вызывают ошибку на этапе компиляции.

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

// Включаемый файл для потокового ввода/вывода

#include <stdio.h>


int main(void) {

// Объявляем две константы

 const int max_nuber = 256;

 // Выводим текстовую строку на экран

 printf("Const Number is %d \n", max_nuber);

 return 0;

}

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

int iNumber;

int *const ptrNumber = &iNumber;

Ссылки

В языке Си++ вы можете определить ссылку на объект – переменную или объект класса. Ссылка содержит адрес объекта, но вы можете использовать ее, как будто она представляет сам объект. Для объявления ссылки используется оператор &.

В следующей программе мы определили переменную iVar типа int и ссылку iReferenceVar на нее. Затем мы отображаем и изменяем значение переменной iVar используя ее имя и ссылку.

// Включаемый файл для потокового ввода/вывода

#include <iostream.h>


void main(void) {

 // Определяем переменную iVar

 int iVar = 10;


 // Определяем ссылку iReferenceVar на переменную iVar

 int& iReferenceVar = iVar;


 // Отображаем значение переменной и ссылки

 cout << "iVar = " << iVar << ";

 iReferenceVar = " << iReferenceVar << '\n';


 // Изменяем значение переменной iVar пользуясь ссылкой

 iReferenceVar = 20;


 // Отображаем значение переменной и ссылки

 cout << "iVar = " << iVar << ";

 iReferenceVar = " << iReferenceVar << '\n';

}

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

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

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

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

В Си++ встроены специальные операторы для управления памятью – оператор new и оператор delete . Эти операторы очень удобны для динамического создания переменных, массивов и объектов классов, поэтому мы остановимся на них более подробно.

Операторы new и delete

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

new type-name [initializer];

new (type-name) [initializer];

В качестве аргумента type-name надо указать имя типа создаваемого объекта. Дополнительный аргумент initializer позволяет присвоить созданному объекту начальное значение. Вот простой пример вызова оператора new:

char *litera;

int *pi;

litera = new char;

pi = new int(3,1415);

В этом примере оператор new используется для создания двух объектов – одного типа char, а другого типа int. Указатели на эти объекты записываются в переменные litera и pi. Заметим, что объект типа int сразу инициализируется значением 3,1415.

Чтобы освободить память, полученную оператором new, надо вызвать оператор delete. Вы должны передать оператору delete указатель pointer, ранее полученный оператором new:

delete pointer;

Оператор new позволяет создавать объекты не только простых типов, он может использоваться для динамического создания массивов. Следующий фрагмент кода создает массив из ста элементов типа long. Указатель на первый элемент массива записывается в переменную pData:

long *pData = new long[100];

Чтобы удалить массив, созданный оператором new, надо воспользоваться другой формой вызова оператора delete:

delete [] pointer;

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

Перегрузка имен функций

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

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

В качестве примера рассмотрим функции Sqware, предназначенные для вычисления площади прямоугольников и квадратов:

int Sqware(int a, int b);

int Sqware(int a);

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

int Sqware(int a, int b) {

 return (a * b);

}

int Sqware(int a) {

 return (a * a);

}

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

void main() {

 int value;

 value = Sqware(10, 20);

 print(“Площадь прямоугольника равна %d”, value);

 value = Sqware(10);

 print(“Площадь квадрата равна %d”, value);

}

Задание параметров функции по умолчанию

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

Если большинство вызовов функции выполняется с одинаковыми параметрами, это позволяет сократить текст программы, а главное, уменьшить возможность совершения ошибок во время набора параметров функции.

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

int Summa(int first, int second, int third=0, int fourth=0) {

 return(first + second + third + fourth);

}

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

void main() {

 int value1 = 10, value2 = 20, value3 = 30, value4 = 40;

 int result;


 // Вызываем функцию с четырьмя параметрами

 result = Summa(value1, value2, value3, value4);

 print(“Сумма четырех чисел равна %d”, result);

 // Вызываем функцию с тремя параметрами

 result = Summa(value1, value2, value3);

 print(“Сумма трех чисел равна %d”, result);

 // Вызываем функцию с двумя параметрами,

 // последний параметр задается по умолчанию

 result = Summa(value1, value2);

 print(“Сумма первых двух чисел равна %d”, result);

}

Встраивание

В некоторых случаях более удобно и эффективно выполнять подстановку тела функции вместо ее вызова. Непосредственная подстановка тела функции позволит сэкономить время процессора на вызове функции. В языке Си этого можно достичь при помощи директивы препроцессора #define. Однако неправильное использование директивы может стать причиной ошибок.

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

inline unsigned int Invert(unsigned int number) {

 return (~number);

}

Классы

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

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

Объявление класса имеет следующий вид:

class [<tag>]

{

<member-list>

} [<declarators>];

Когда вы определяете класс, то сначала указывается ключевое слово class, а затем в качестве аргумента <tag> имя самого класса. Это имя должно быть уникальным среди имен других классов, определенных в вашей программе.

Затем в фигурных скобках следует список элементов класса <member-list>. В качестве элементов класса могут фигурировать данные (переменные), битовые поля, функции, вложенные классы, а также некоторые другие объекты. Вы можете включить качестве элемента класса указатель на другие объекты этого класса.

Классы образуют собственное пространство имен. Имена элементов одного класса могут совпадать с именами элементов другого класса и даже с именами других переменных и функций определенных в программе.

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

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

[class] tag declarators;

Ключевое слово class перед именем класса можно опустить.

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

Ключевое слово this

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

Разграничение доступа к элементам класса

Определив класс, вы можете создавать объекты этого класса и манипулировать ими, используя методы. Некоторые данные и методы, объединенные одним классом, можно сделать недоступными вне реализации класса, к другим можно будет обращаться из программы.

Для управления доступом к элементам класса предусмотрены ключевые слова public, private и protect (спецификаторы доступа). Методы и данные, определенные или описанные после ключевого слова public представляют собой интерфейс класса – они доступны для использования вне определения класса. Остальные члены класса относятся к его внутренней реализации и обычно недоступны вне класса. Различия между членами класса, описанными после ключевых слов private и protect сказываются только при наследовании от данного класса новых классов. Процедуру наследования мы рассмотрим позже.

Ключевые слова public, private и protect указываются в определении класса перед элементами класса, доступом к которым они управляют. Ключевые слова, управляющие доступом, могут быть указаны несколько раз в одном классе, порядок их расположения значения не имеет. По умолчанию элементы класса являются private. Рекомендуется всегда явно определять права доступа к членам класса.

Ниже представлено определение класса Sample:

class Sample {

 int iX;

 void Load();

public:

 void SetStr();

 void GetStr();

 char sDataText[80];

private:

 char sNameText[80];

 int iIndex;

public:

 void ConvertStr();

 int iLevel;

};

В классе описаны элементы данных iX, sDataText, sNameText, iIndex, iLevel и методы Load, SetStr, GetStr, ConvertStr.

Элементы данных и методы SetStr, GetStr, sDataText, ConvertStr, iLevel объявлены public. К ним можно обращаться как из методов класса Sample, так и из программы. Остальные элементы класса объявлены как private. Доступ к ним открыт только для методов самого класса, а также дружественных функций и дружественных методов других классов. Дружественные функции и дружественные классы описаны в следующем разделе.

Методы, входящие в класс

Если исходный текст метода очень короткий, то такой метод обычно определяется непосредственно внутри класса. Вы можете указать, что вместо вызова необходимо выполнять подстановку его тела. Для этого перед ее объявлением следует указать ключевое слово inline. Вот пример определения методов SetWeight и GetWeight непосредственно внутри класса:

class line {

public:

 void SetLength(int newLength) { length = newLength; }

 int GetLength() { return length; }

private:

 int length;

};

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

class convert {

public:

 void GetString() { scanf(sText, "%s"); }

 void ShowString() { puts(sText); }

 int ConvertString();

 void DummyString();

private:

 char sText[80];

};

void convert::ConvertString(void) {

 int i;

 for (i = 0; sText[i] != ‘\0’; i++ ) {

  sText[i] = tolower(sText[i]);

 }

 return i;

}

inline void convert::DummyString(void) {

 int i = 0;

 while (sText[i++]) sText[i] = 0;

}

Чтобы вызвать метод, надо сначала указать имя объекта класса, для которого будет вызван метод, а затем через точку имя метода. Вместо имени объекта можно использовать указатель на объект. В этом случае вместо символа точки надо использовать оператор –>. Если метод вызывается из другого метода этого же класса, то имя объекта и оператор выбора элемента указывать не надо.

Следующий пример демонстрирует вызов методов класса convert, исходный текст которого приведен выше:

void main() {

 convert ObjectA;

 ObjectA.GetString();

 ObjectA.ConvertString();

 ObjectA.ShowString();

 convert *pObjectB = new convert;

 pObjectB->GetString();

 pObjectB->ConvertString();

 pObjectB->ShowString();

}

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

Конструкторы и деструкторы класса

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

Язык С++ предоставляет удобное средство для инициализации и удаления объектов класса. Для этого предусмотрены специальные методы. Они называются конструкторами и деструкторами.

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

Класс BookList, представленный ниже, имеет два конструктора BookList. Первый конструктор не имеет параметров, второй конструктор имеет один параметр типа int:

class BookList {

// Конструкторы класса

void BookList();

void BookList(int);

// Остальные члены класса

};

// Первый конструктор класса

BookList::BookList(void) { }

// Второй конструктор класса

BookList::BookList(int iList) { }

Когда вы создаете объекты класса, вы можете указать параметры для конструктора. Ниже создаются два объекта класса BookList – FirstBook и SecondBook:

BookList FirstBook;

BookList SecondBook(100);

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

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

Ниже представлен класс Object, для которого определен деструктор ~Object:

class Object {

void ~Object();

// Остальные члены класса

};


Object::~Object(void) { }

Методы, не изменяющие объекты класса

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

Методы, объявленные как const, не могут изменять элементы класса или вызывать другие методы, объявленные без ключевого слова const. Нарушение этих правил вызовет ошибку на этапе компиляции приложения.

В библиотеке классов MFC вы встретите много методов, объявленных как const. Их использование повышает надежность приложения, так как компилятор сможет обнаружить ошибки, связанные с непреднамеренным изменением элементов класса.

Ниже мы привели пример класса, для которого метод GetWeight определен как const. Если вы попытаетесь модифицировать элемент данных weight непосредственно из метода GetWeight, компилятор сообщит об ошибке.

#include <iostream.h>


void main(void);


// Класс ClassMen включает элемент данных и два метода для

// обращения к нему

class ClassMen {

public:

 void SetWeight(int newWeight);

 int GetWeight() const;

private:

 int weight;

};


// Метод GetWeight позволяет определить значение элемента

// weight. Этот метод объявлен как const и не может

// модифицировать объекты класса ClassMen

int ClassMen::GetWeight() const {

 return weight ;

}


// Метод SetWeight позволяет изменить значение weight.

// Такой метод нельзя объявлять как const

void ClassMen::SetWeight(int newWeight) {

 weight = newWeight;

}


// Главная функция программы

void main(void) {

 // Создаем объект класса ClassMen

 ClassMen alex;


 // Устанавливаем значение элемента weight объекта alex

 alex.SetWeight(75);


 // Отображаем значение элемента weight объекта alex

 cout << alex.GetWeight() << "\n";

}

Статические методы

Вы можете объявить некоторые методы класса статическими методами. Для этого вы должны воспользоваться ключевым словом static. Статические методы не принимают параметр this. На использование статических методов накладывается ряд ограничений.

• Статические методы могут непосредственно обращаться только к статическим членам класса.

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

• Вы не можете определить нестатический метод с тем же именем и тем же набором параметров, что и статический метод класса.

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

Ниже представлен класс Circle, в котором определена статический метод GetPi. Он используется для получения значения статического элемента класса fPi.

class Circle {

public:

static void GetPi() { return fPi; }


private:

static float fPi;

};

float Circle::fPi = 3.1415;

Вы можете вызвать метод GetPi следующим образом:

float fNumber;

fNumber = Circle::GetPi();

Обратите внимание, что объект класса Circle не создается.

Общие члены объектов класса

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

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

class CWindow {

public:

 int xLeftTop, xRightBottom;

 int yLeftTop, yRightBottom;

 static char title[80];

 void SetTitle(char*);

};

char Cwindow::title[80] = "заголовок окна";

Каждый объект класса Cwindow будет иметь уникальные координаты, определяемые элементами данных xLeftTop, xRightBottom, yLeftTop, yRightBottom и одинаковый заголовок, хранимый элементом данных title.

Общие элементы данных находятся в области действия своего класса. Методы класса могут обращаться к общим элементам точно так же, как к остальным данным из класса:

void SetTitle(char* sSource) {

 strcpy(title, sSource);

}

Чтобы получить доступ к общим элементам из программы, надо объявить их как public. Для обращения к такой переменной перед ее именем надо указать имя класса и оператор ::.

printf(Cwindow::title);

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

Дружественные функции
В Си++ вы можете определить для класса так называемую дружественную функцию, воспользовавшись ключевым словом friend. В классе содержится только объявление дружественной функции. Ее определение расположено вне класса. Вы можете объявить дружественную функцию в любой секции класса – public, private или protect.

Дружественная функция не является элементом класса, но может обращаться ко всем его элементам, включая private и protect. Одна и та же функция может быть дружественной для двух или более классов.

В следующем примере определена функция Clear, дружественная для класса point. Дружественная функция Clear используется для изменения значения элементов данных m_x и m_y, объявленных как private:

//==========================================================

// Класс point

class point {

public:

// Функция Clear объявляется дружественной классу point

 friend void point::Clear(point*);


 // Интерфейс класса…

 private:

 int m_x;

 int m_y;

};


//==========================================================

// Функция Clear

void Clear(point* ptrPoint) {

 // Обращаемся к элементам класса, объявленным как private

 ptrPoint->m_x = 0;

 ptrPoint->m_y = 0;

 return;

}


//==========================================================

// Главная функция

void main() {

 point pointTestPoint;


 // Вызываем дружественную функцию

 Clear(&pointTestPoint);

}

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

В следующем примере мы определяем два класса – line и point. В классе point определяем метод Set и объявляем его в классе line как дружественный. Дружественный метод Set может обращаться ко всем элементам класса line:

// Предварительное объявление класса line

class line;


//==========================================================

// Класс point

class point {

public:

 // Метод Set класса point

 void Set(line*);


 // …

};


//==========================================================

// Класс line

class line {

public:

 // Метод Set класса point объявляется дружественной

 // классу point

 friend void point::Set(line*);


private:

 int begin_x, begin_y;

 int end_x, end_y;

};


//==========================================================

// Функция Clear

void point::Set(line* ptrLine) {

 // Обращаемся к элементам класса line, объявленным как

 // private

 ptrLine->begin_x = 0;

 ptrLine->begin_y = 0;

 // …


 return;

}


//==========================================================

// Главная функция

void main() {

 point pointTestPoint;

 line lineTestPoint;


// Вызываем дружественный метод

 pointTestPoint.Set(&lineTestPoint);

}

Дружественные классы
По аналогии с дружественными функциями и методами, можно объявить дружественный класс. Все методы дружественного класса, могут обращаться ко всем элементам класса, включая элементы, объявленные как private и protect.

Так, например, в предыдущем примере вы могли бы определить, что класс point является дружественным классу line. Все методы класса point могут обращаться к любым элемента класса line.

//==========================================================

// Класс point

class point {

 // …

};


//==========================================================

// Класс line

class line {

public:

 // Класс point объявляется дружественным классу line

 friend class point;

};

Наследование

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

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

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

От одного общего базового класса можно наследовать несколько новых производных классов. Производный класс сам может служить базовым классом для новых классов. Таким образом возможна древовидная структура наследования классов.

На рисунке 1.1 мы привели пример структуры наследования классов. От единственного базового класса BaseClass наследуются три класса DerivedClassOne, DerivedClassSecond и DerivedClassThird. Первые два из них сами выступают в качестве базовых классов.

Рис. 1.1. Наследование


Практически вся структура библиотеки классов MFC основывается на механизме наследования. Так, например, большинство классов MFC имеет базовый класс CObject, который обеспечивает наиболее общие свойства унаследованных от него классов.

Можно выделить две основных формы наследования. Когда производный класс наследуется от единственного базового класса – единичное наследование. Наследование, при котором производный класс наследуется сразу от нескольких базовых классов – это множественное наследование .

Единичное наследование

В случае единичного наследования порожденный класс наследуется только от одного базового класса. Рисунок 1.1 отражает единичное наследование классов. Единичное наследование является наиболее распространенным методом наследования. Библиотека классов MFC использует только единичное наследование.

Чтобы указать, что класс наследуется от другого базового класса, имя базового класса <base> необходимо указать после имени класса перед открывающей фигурной скобкой определения класса. Непосредственно перед именем базового класса необходимо поставить знак двоеточия:

class [<tag>[:<base>]]

{

<member-list>

} [<declarators>];

Перед названием базового класса может быть указан спецификатор доступа public, private или protect. Назначение этих спецификаторов мы рассмотрим в разделе “Разграничение доступа к элементам базового класса”. Сейчас же мы скажем только, что если вы не укажите спецификатор доступа, то по умолчанию будет подразумеваться спецификатор private.

Ниже мы определили базовый класс Base, содержащий несколько элементов, а затем наследовали от него два новых класса DerivedFirst и DerivedSecond. В каждом из порожденных классов мы определили различные дополнительные методы и элементы данных.

// Класс Base

class Base {

 // Элементы класса Base

};


// Класс DerivedFirst, наследованный от базового класса Base

class DerivedFirst : Base {

 // Элементы класса DerivedFirst

};


// Класс DerivedSecond, наследованный от базового класса Base

class DerivedSecond : Base {

 // Элементы класса DerivedSecond

};

Классы DerivedFirst и DerivedSecond сами могут выступать в качестве базовых классов.

Вы можете определять в пороженном классе элементы, имена которых совпадают с именами элементов базовых классов. Если вы выполнили такое переопределение, вы можете обратиться к элементу базового класса, если укажете его полное имя. Полное имя должно состоять из имени класса, к которому относится элемент, оператора :: и имени самого элемента.

В качестве примера приведем базовый класс Base и производный от него класс Derived. В обоих классах определен элемент данных iNumber. Чтобы получить доступ из порожденного класса к элементу iNumber базового класса указывается его полное имя Base::iNumber.

// Класс Base

class Base {

public:

 int iNumber;

 // Другие элементы класса

};


// Класс Derived, наследованный от базового класса Base

class Derived : Base {

public:

 // Это объявление скрывает элемент iNumber базового класса

 int iNumber;

 int GetNumber(void) { return iNumber + Base::iNumber; }

};

Указатель на объект базового класса можно присвоить указатель на объект класса порожденного от него. Эта возможность будет широко использоваться в библиотеке классов MFC.

Множественное наследование

Множественное наследование выполняется подобно единичному наследованию. В отличие от единичного наследования у порожденного класса может быть несколько базовых классов. На рисунке 1.2 представлен пример множественного наследования классов. Класс DerivedClaass имеет два базовых класса BaseClassOne и BaseClassSecond. Класс DerivedClaass и еще один класс BaseClass используются при множественном наследовании класса DerivedClaassSecond.

Рис. 1.2. Множественное наследование


Вместо имени единственного базового класса указывается список <base-list> имен базовых классов, разделенный запятыми. Непосредственно перед названиями базовых классов могут быть указаны спецификаторы доступа public, private и protect. Их назначение мы рассмотрим в разделе “Разграничение доступа к элементам базового класса”.

class [<tag>[:[<base-list>]]

{

<member-list>

} [<declarators>];

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

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

В следующем примере определены два базовых класса BaseFirst и BaseSecond. От них наследован один новый класс Derived. Результирующий класс Derived объединяет элементы обоих базовых классов и добавляет к ним собственные элементы.

// Класс BaseFirst

class BaseFirst {

// Элементы класса BaseFirst

};


// Класс BaseSecond

class BaseSecond {

// Элементы класса BaseSecond

};

// Класс Derived, наследованный от базового класса Base

class Derived : BaseFirst, BaseSecond {

// Элементы класса Derived

};

Так как библиотека классов MFC не использует множественное наследование, мы не станем останавливаться на нем более подробно. При необходимости вы можете получить дополнительную информацию из справочников или учебников по языку Си++ (см. список литературы).

Разграничение доступа к элементам базового класса

Мы уже рассказывали, что можно управлять доступом к элементам класса, указывая спецификаторы доступа для элементов класса. Элементы класса, объявленные с спецификаторами protected и private доступны только из методов самого класса. Элементы с спецификаторами public доступны не только из методов класса, но и извне.

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

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

Спецификатор доступа базового класса
Спецификатор доступа элемента базового класса public protected private
public Доступны как public Доступны как protected Доступны как private
protected Доступны как protected Доступны как protected Доступны как private
private Недоступны Недоступны Недоступны

Переопределение методов базового класса

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

Виртуальные методы

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

В Си++ вы можете указать, что некоторые методы базового класса, которые будут переопределены в порожденных классах, являются виртуальными. Для этого достаточно указать перед описанием метода ключевое слово virtual. Статический метод не может быть виртуальным. Методы, объявленные в базовом классе виртуальными считаются виртуальными и в порожденных классах.

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



Все права на текст принадлежат автору: Александр Вячеславович Фролов, Григорий Вячеславович Фролов.
Это короткий фрагмент для ознакомления с книгой.
Microsoft Visual C++ и MFC. Программирование для Windows 95 и Windows NTАлександр Вячеславович Фролов
Григорий Вячеславович Фролов