Вы находитесь на странице: 1из 106

2012

Программирование
на Visual C++
Методическое пособие
Курс для тех, кто умеет программировать на языке C, хочет изучить C++
в среде Microsoft Visual C++ и приобрести базовые навыки разработки
приложений под Windows

© Тетерин Вячеслав Гертрудович


Центр «Специалист» www.specialist.ru
Москва, 2012
2 Программирование на Visual C++

Оглавление

Введение ................................................................................................................................................ 7

Особенность курса ........................................................................................................................... 7

Краткая хронология C++.................................................................................................................. 7

Языки C++ и C ................................................................................................................................... 8

Модуль 1. Типы данных, операторы и функции в C++ .................................................................. 9

Ссылочный тип данных ................................................................................................................... 9

Оператор расширения контекста ................................................................................................. 10

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

Встраиваемые функции ................................................................................................................. 12

Перегрузка функций. Аргументы по умолчанию ....................................................................... 12

Новые заголовочные файлы ........................................................................................................ 13

Потоковый ввод-вывод в C++....................................................................................................... 14

Описание переменных, массивов и других типов ..................................................................... 15

Операторы приведения типа ........................................................................................................ 16

Новые типы данных и операторы ................................................................................................ 17

Задачи .............................................................................................................................................. 18

Вопросы для обсуждения и «углубления» ................................................................................. 18

Модуль 2. Инкапсуляция ................................................................................................................... 21

Понятие класса ............................................................................................................................... 21

Определение класса ...................................................................................................................... 21

Спецификаторы доступа к компонентам класса ....................................................................... 21

Пример – класс Point (начало)................................................................................................... 22

Определение экземпляров класса (объектов) .......................................................................... 23

Доступ к компонентам класса. ...................................................................................................... 23

Указатель this .................................................................................................................................. 24


Центр «Специалист» www.specialist.ru
Программирование на Visual C++ 3

Дружественные функции ............................................................................................................... 25

Дружественные классы.................................................................................................................. 25

Пример – класс Point (продолжение) .......................................................................................... 25

Задачи .............................................................................................................................................. 28

Вопросы для обсуждения и «углубления» ................................................................................. 28

Модуль 3. Специальные методы класса ........................................................................................ 29

Понятие конструктора класса ....................................................................................................... 30

Виды конструкторов ....................................................................................................................... 30

Инициализация объекта в конструкторе .................................................................................... 31

Вызов конструкторов...................................................................................................................... 31

Понятие деструктора класса. Вызов деструктора .................................................................... 32

Оператор присваивания ................................................................................................................ 32

Пример – класс Point (продолжение) .......................................................................................... 32

Пример – класс String (начало) .................................................................................................... 35

Задачи .............................................................................................................................................. 38

Вопросы для обсуждения и «углубления» ................................................................................. 38

Модуль 4. Перегрузка операторов (операций) .............................................................................. 43

Перегрузка операторов.................................................................................................................. 43

Особенности перегрузки операторов ++ и -- ........................................................................... 44

Пример – класс String (продолжение) ......................................................................................... 45

Потоковый ввод-вывод в языке C++. Общие положения ........................................................ 47

Важнейшие классы стандартных потоков ввода-вывода ........................................................ 47

Операторы обмена данными и манипуляторы ......................................................................... 48

Ввод-вывод пользовательских типов.......................................................................................... 49

Основы работы с файловыми потоками .................................................................................... 49

Пример – класс String (окончание) .............................................................................................. 50

Центр «Специалист» www.specialist.ru


4 Программирование на Visual C++

Задачи .............................................................................................................................................. 51

Вопросы для обсуждения и «углубления» ................................................................................. 51

Модуль 5. Статические элементы данных ..................................................................................... 53

Статические поля и методы класса ............................................................................................ 53

Определение и использование статического поля .................................................................. 53

Объявление и вызов статического метода ................................................................................ 53

Константные объекты и методы .................................................................................................. 54

Пример – класс Pixel ...................................................................................................................... 55

Задачи .............................................................................................................................................. 58

Вопросы для обсуждения и «углубления» ................................................................................. 58

Модуль 6. Наследование и полиморфизм ..................................................................................... 59

Понятие наследования в ООП и его реализация в Си++ ........................................................ 59

Определение производного класса............................................................................................. 59

Пример – класс Pixel ...................................................................................................................... 60

Виртуальные функции ................................................................................................................... 62

Пример – классы Point и Pixel ...................................................................................................... 62

Чистые виртуальные функции и абстрактные базовые классы ............................................. 62

Пример – класс Shape ................................................................................................................... 63

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

Пример – класс Marker .................................................................................................................. 64

Виртуальные базовые классы ...................................................................................................... 65

Пример – класс WorkingStudent ................................................................................................... 65

Задачи .............................................................................................................................................. 68

Вопросы для обсуждения и «углубления» ................................................................................. 69

Модуль 7. Шаблоны функций и классов......................................................................................... 70

Назначение и использование шаблонов функций.................................................................... 71

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 5

Пример – шаблон функции max ................................................................................................... 71

Назначение и использование шаблонов классов ..................................................................... 72

Пример – шаблон класса Stack .................................................................................................... 73

Задачи .............................................................................................................................................. 74

Вопросы для обсуждения и «углубления» ................................................................................. 75

Модуль 8. Управление исключениями ............................................................................................ 76

Понятие исключения. Обработка исключительных ситуаций ................................................ 76

Пример – класс String (с обработкой исключений) ................................................................... 77

Динамическая идентификация типов (RTTI).............................................................................. 79

Оператор приведения типов dynamic_cast<>()................................................................... 79

Пример – Полиморфные типы и операторы typeid() и dynamic_cast<>() ............................ 80

Задачи .............................................................................................................................................. 81

Вопросы для обсуждения и «углубления» ................................................................................. 82

Модуль 9. Использование Microsoft Visual Studio ......................................................................... 84

Интерфейс Visual Studio ................................................................................................................ 84

Создание консольного приложения и приложения Win32....................................................... 84

Понятие проекта и просмотр компонентов проекта ................................................................. 84

Виды ресурсов Visual Studio ......................................................................................................... 85

Формирование визуального графического интерфейса в редакторе ресурсов .................. 85

Структура проекта .......................................................................................................................... 86

Пример – Визуальное проектирование диалогового окна ...................................................... 86

Задачи .............................................................................................................................................. 87

Вопросы для обсуждения и «углубления» ................................................................................. 87

Модуль 10. Структура приложения Windows ................................................................................. 88

Структура приложения Windows .................................................................................................. 88

Главная функция Windows-приложения WinMain ..................................................................... 88

Центр «Специалист» www.specialist.ru


6 Программирование на Visual C++

Механизм сообщений Windows, цикл обработки сообщений ................................................. 88

Функция управления окном приложения .................................................................................... 89

Сообщения, посылаемые окну приложения, и их обработка ................................................. 89

Пример – Windows-приложение, построенное по шаблону Win32 Application .................... 90

Задачи .............................................................................................................................................. 93

Вопросы для обсуждения и «углубления» ................................................................................. 94

Модуль 11. Графика под Windows ................................................................................................... 96

Контекст устройства ....................................................................................................................... 96

Обработка сообщения WM_PAINT .............................................................................................. 96

Вывод графических образов (примитивов) ................................................................................ 96

Битовые образы .............................................................................................................................. 97

Пример – Вывод графических примитивов ................................................................................ 97

Задачи .............................................................................................................................................. 99

Вопросы для обсуждения и «углубления» ................................................................................. 99

Модуль 12. Итоговое занятие......................................................................................................... 100

Приложение ....................................................................................................................................... 101

Пример – рисование мышью ...................................................................................................... 101

Пример – поддержка стандартных диалогов........................................................................... 103

Литература......................................................................................................................................... 106

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 7

Введение

Особенность курса
Курс для тех, кто умеет программировать на языке C, хочет изучить C++ в среде Microsoft Visual C++ и
приобрести базовые навыки разработки приложений под Windows.

Краткая хронология C++


1979 —Бьярн Страуструп (Bjarne Stroustrup) в AT&T BellLabs приступает к разработке и создает первую
реализацию языка «Си с классами» (C with Classes).

1983 — Новый язык получает современное название C++.

1985 — Выпуск Cfront Release 1.0 – первая коммерческая версия языка. Опубликована книга Б. Страу-
струпа «The C++ Programming Language».

1989 —Выпуск Cfront Release 2.0.

1990 — Опубликована книга «The Annotated C++ Reference Manual» (ARM), надолго ставшая нефор-
мальным стандартом языка C++.

1991 — Опубликовано второе издание «The C++ Programming Language (2nd)». Выпуск Cfront Release
3.0, в котором реализована поддержка шаблонов.

1994 — В комитете ANSI/ISO зарегистрирован предварительный стандарт языка C++. Одобрено вклю-
чение в стандарт библиотеки шаблонов контейнеров, итераторов и фундаментальных алгоритмов
(STL).

1997 — Опубликовано третье издание«The C++ Programming Language (3rd)».

1998 —Принят стандарт – ISO/IEC 14882:1998(E), Programming Language C++, известный как C++98.

2000 — Опубликовано третье специальное издание «The C++ Programming Language (Special 3rd
Edition)».

2003 — Опубликованы технические поправки к стандарту C++ – ISO/IEC 14882:2003(E) updated ISO and
ANSI C++ Standard, известные как C++03.

2006 — Опубликован технический отчет с одобренными расширениями стандартной библиотеки C++


ISO/IEC TR19768: C++ Library Extensions TR1.

2011 — Принят новый стандарт – ISO/IEC 14882:2011, Programming Language C++, получивший назва-
ние C++11.

Примечание. Так как распространенные компиляторы пока еще не поддерживают новый стандарт в полном
объеме, ниже речь будет идти о стандартах C++98, C++03.

Центр «Специалист» www.specialist.ru


8 Программирование на Visual C++

Языки C++ и C
Язык C++ является надмножеством языка C. Важнейшими расширениями языка C, превращающими
его в новый мощный язык C++ являются:

- классы, предоставляющие возможности создания и встраивания в язык новых типов данных и под-
держки парадигмы объектно-ориентированного программирования (инкапсуляции, наследования, по-
лиморфизма);

- шаблоны, реализующие поддержку обобщенного (родового, параметрического) программирования;

- пространства имен, упрощающие разработку крупномасштабных модульных приложений;

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


щейся многомодульной программы;

- расширяемая стандартная библиотека шаблонов, предлагающая множество готовых и эффектив-


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

Кроме того, сам язык C, лежащий в основании C++, пополнился новыми возможностями, возникшими,
прежде всего, из потребности поддержки классов и шаблонов, но которые могут использоваться и сами
по себе. Благодаря этим средствам, программный код, написанный на C-подмножестве языка C++, ста-
новится более наглядным, более гибким и более надежным. По этой причине C-подмножество языка
C++ часто называют «улучшенный C».

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 9

Модуль 1. Типы данных, операторы и функции в C++


В этом модуле рассматриваются наиболее существенные «улучшения» в C-подмножестве языка C++.

Ссылочный тип данных


Ссылка вводит псевдоним («второе имя») для существующего объекта и внутренне реализована как
«саморазадресуемый» указатель.

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

Определение ссылки на переменную конкретного типа данных:

тип &идентификатор = инициализатор;

Для инициализации ссылок используются Lvalue – «адресующие выражения». Значение ссылки по-
сле определения — адрес существующего объекта.

Ссылка на константу может инициализироваться Rvalue – «вычисляющим значение выражением», в


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

//Listing 1.1. Определение ссылки


void demo_ref_1()
{
int a = 100;
int &r = a; //инициализация при создании, после чего все
//операции над r относятся к a
int b = r; //эквивалентно b = a;
int *p = &r; //эквивалентно p = &a;
++r; //эквивалентно ++a;
const int &cr = a; //ссылка на константу (для доступа read only)
b = cr + 1; //OK
b = ++cr; //ERROR
}
Ограничения в использовании ссылок:

- не существует конструкций «ссылка на ссылку», «указатель на ссылку» и «массив ссылок», но можно


создать «ссылку на указатель»:

тип *&идентификатор = инициализатор;

и ссылку на функцию:

тип (&идентификатор)(формальные_аргументы) = инициализатор;

Центр «Специалист» www.specialist.ru


10 Программирование на Visual C++

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

//Listing 1.2. Ссылки в качестве аргументов функции


void swap(int &a, int &b) //a и b - псевдонимы фактических параметров
{
int t = a; a = b; b = t;
}

void demo_ref_2()
{
int x = 100, y = 200;
swap(x, y); //обмен значений x и y
}

Ссылка может быть возвращаемым из функции значением.

//Listing 1.3. Ссылка в качестве возвращаемого значения функции


int &max(int &a, int &b)
{
return a > b ? a : b;
}
void demo_ref_3()
{
int x = 100, y = 200;
int m = max(x,y);
}

Оператор расширения контекста


Оператор расширения контекста (оператор ::) применяется для спецификации (уточнения) области
видимости имен (идентификаторов).

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

В бинарном формате он специфицирует обращение к имени, принадлежащему области видимости


класса или пространства имен.

//Listing 1.4. Оператор :: расширения контекста


#include<iostream> //заголовочный файл библиотеки ввода/вывода С++
//в стандартном стиле, все библиотечные имена
//определены в пространстве имен std
double a = 12.345; //имя a принадлежит глобальному пространству имен

void demo_scope()
{
int a = 100; //это имя a- локальное в блоке
std::cout<<"Local a = "<< a<<std::endl; //имена cout, endl из std
std::cout<<"Global a = "<<::a<<std::endl; //имя a - глобальное
}

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 11

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


Операторы new и delete служат для динамического распределения памяти и являются более удобной
и более безопасной альтернативой использованию стандартных ANSI-функций языка C malloc() и
free().

Выражение с оператором new имеет два формата:

new тип

new тип(инициализатор)

и возвращает адрес выделенного блока или, в случае невозможности выделения, возбуждает исключе-
ние типа std::bad_alloc (до принятия стандарта в этом случае возвращалось значение NULL).

Выражение с оператором delete имеет вид:

delete указатель

Разрешено применение оператора delete к нулевому указателю.

Для распределения памяти под массивы используются операторы new [] и delete []:

new тип[размер_массива]

delete [] указатель

//Listing 1.5. Операторы new и delete - работа с динамической памятью


void demo_heap(int init_val, int arr_size)
{
int *p = new int(init_val); //выделение памяти под int в heap
//init_val - инициализатор
// здесь может быть отказ в выделении памяти.
// C++ имеет специальный механизм обработки подобных
// "исключительных ситуаций" (exceptions)
//... использование *p...

delete p; //возврат памяти, выделенной объекту


p = new int[arr_size]; //выделение в heap массива

// здесь может быть отказ в выделении памяти

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


{
//... использование p[j] ...
}

delete[] p; //возврат памяти, выделенной массиву


}

Центр «Специалист» www.specialist.ru


12 Программирование на Visual C++

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

Функция не может быть встраиваемой, если она, например,

- является рекурсивной

- в области видимости вызова вычисляется адрес этой функции

- слишком велика для подстановки в объектный код (с «точки зрения» компилятора), то есть, если она
содержит сложные или вложенные циклы, переключатели и т.п.

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

При невозможности встраивания функция вызывается как «обычная» функция.

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

inline тип идентификатор(формальные_аргументы)

{ тело_функции }

Определение встраиваемой функции должно быть «видимо» компилятору в точке вызова.

//Listing 1.6. Встраиваемая функция


inline int min(int a, int b)
{
return a < b ? a : b;
}
int demo_inline(int x, int y, int z)
{
return min(min(x,y),z);
}

Перегрузка функций. Аргументы по умолчанию


В C++ запрещен вызов функции без ее предварительного объявления (прототипа) или полного опреде-
ления.

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

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 13

Распознавание перегруженных функций при вызове происходит по критерию «наилучшего соответст-


вия» сигнатуры выражению вызова. В случае нахождения нескольких «наилучших» кандидатов вызов
считается неоднозначным.

//Listing 1.7. Перегруженные функции


int min(int, int);
int min(int, int, int);
int min(const int *, int);

void demo_overload(int x, int y, int z, int a[],int n)


{
int m1 = min(x,y);
int m2 = min(x, y, z);
int m3 = min(a, n);
}

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

тип идентификатор = инициализатор

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


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

//Listing 1.8. Аргументы со значением по умолчанию


#include <iostream>
void print(const int *array, int size, const char *delimeter = ", ");
void demo_default_arg(int arr[],int n)
{
print(arr,n); //распечатка массива через запятую
print(arr,n," : "); //распечатка массива через двоеточие
print(arr,n,"\n"); //распечатка массива «в столбец»
}
void print(const int *ar, int sz, const char *delim) //здесь умолчание
{ //уже не задается
while(sz--) std::cout << *ar++ << (!sz ? "\n" : delim);
}

Неиспользуемые в теле функции формальные аргументы могут объявляться в заголовке функции без
имени.

Новые заголовочные файлы


Стандарт определил, что заголовочные файлы библиотеки C++ не имеют расширения .h и что унасле-
дованные файлы стандартной библиотеки C тоже именуются без расширения, но предваряются пре-
фиксом ―c‖.

Центр «Специалист» www.specialist.ru


14 Программирование на Visual C++

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

Директива using namespace std (популярная в учебных примерах) предписывает компилятору про-
должить поиск имен, не описанных в текущем файле, в пространстве std, что фактически приводит к
экспорту всех имен стандартной библиотеки в глобальное пространство имен.

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

Заголовочные файлы, не относящиеся к пространству имен std, в том числе «собственные» файлы
программистов, по-прежнему имеют расширение.h.

//Listing 1.9. Заголовочные файлы


/*
#include <iostream.h> //заголовочный файл библиотеки ввода/вывода С++
#include <stdlib.h> //и заголовочный файл библиотеки языка С
*/ //в достандартном стиле

#include <iostream> //заголовочный файл библиотеки ввода/вывода С++


//в стандартном стиле
#include <cstdlib> //заголовочный файл библиотеки языка С
//в стандартном стиле

#include "my_file.h" //нестандартные заголовочные файлы по-прежнему


//имеют расширение .h
using namespace std; //директива включения имен стандартной
//библиотеки в глобальное пространство имен
/*
using std::cout; //объявление using включает только указанное имя
using std::cin;
using std::endl;
*/

Потоковый ввод/вывод в C++


Для ввода/вывода данных C++ предлагает альтернативу унаследованной низкоуровневой библиотеке
ANSI C функций из <cstdio>, а именно, высокоуровневую безопасную по отношению к типам и рас-
ширяемую библиотеку объектов:

cin с перегруженным оператором >> для ввода данных из стандартного входного потока (обычно с
клавиатуры);

cout с перегруженным оператором << для вывода данных в стандартный выходной поток (обычно на
экран монитора);

cerr с перегруженным оператором << для вывода данных в выходной поток сообщений об ошибках
(на экран монитора).

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 15

//Listing 1.10. Программа «Hello, world»


#include<iostream> //заголовочный файл библиотеки ввода/вывода С++
using namespace std; //директива включения имен стандартной библиотеки
int main()
{
cout << "Hello, world!" << endl;
//инструкцию return 0; в конце main компилятор C++ впишет сам
}

В этом листинге: std::cout – объект ―выходной поток‖;


<< - оператор "вставки в поток";
std::endl - функция-манипулятор ―конец строки‖.

Операторы "вставки в поток" и "извлечения из потока" можно использовать «цепочкой.

Описание переменных, массивов и других типов


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

Константные переменные могут использоваться для указания размера массива.

Имена (теги) структур, объединений, перечислений (и классов) являются полноценным именами новых
типов и не требуют обязательного, как в C, указания ключевого слова struct , union, enum (и class).

//Listing 1.11. Инструкции описания


#include <iostream>
#include <cstdlib>
using namespace std;
int main()
{
cout <<"Enter int and float values: ";
int a;
double b;
cin >> a >> b; //std::cin - объект “входной поток”
// >> - оператор "извлечение из потока"
//<< и >> часто применяют "цепочкой"

cout << "a=" << a << ", b=" << b << endl;
cout << "a > b is " //можно выводить и результат вычислений,
<< (a > b) << endl; //но с учетом приоритетов операторов
const int n = 10;
int arr[n]; //размер массива – const-переменная

for(int i=0; i<n; ++i) //определение переменной i в for


arr[i] = rand() % 100;

Центр «Специалист» www.specialist.ru


16 Программирование на Visual C++

struct date
{
int day, month, year;
};

date today; //тип date (без struct и typedef)


}

Операторы приведения типа


Приведения между различными типами, безопасные с точки зрения системы типов C++, выполняются
компилятором неявно. Но «опасные» преобразования не компилируются – для них требуется произве-
сти явные приведения типов.

В C++, наряду с унаследованной из C формой оператора явного приведения типа, имеется и функцио-
нальная форма его записи. Так вместо (double)value можно использовать double(value). Одна-
ко обе эти формы не рекомендуются к использованию. Вместо них предлагается применять новые опе-
раторы явного приведения типа:

static_cast<>() – для приведений между арифметическими типами, а также между указателями


или ссылками.

const_cast<>() – для отмены квалификаторов const и volatile.

reinterpret_cast<>() – для системно-зависимых приведений.

dynamic_cast<>() – для приведений полиморфных указателей и ссылок при работе с классами (он
будет рассмотрен позже).

Константа 0 в контексте работы с указателями неявно преобразуется в NULL.

//Listing 1.12. Операторы приведения типа


#include <cstdlib>
using namespace std;
void demo_casting()
{
int a = 10;
int *p = &a;

const int *cp = p; //неявное приведение


p = const_cast<int *>(cp); //требуется явное приведение
void *vp = p; //неявное приведение
p = static_cast<int *>(vp); //требуется явное приведение
p = 0; //неявное приведение 0 к NULL

int n = reinterpret_cast<int>(p);//требуется явное приведение


}

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 17

Новые типы данных и операторы


Новый тип – логический определяется при помощи описателя bool и имеет значения true и false.

Новый тип – указатель на компонент структуры/класса и операторы для работы с ним. Хотя этот тип и
может, но практически не находит применения в C-подмножестве, так как, главным образом, он введен
в C++ для поддержки специальных задач при работе с классами.

Относительно еще одного нового оператора, typeid(), справедливо аналогичное утверждение. Он


тоже, хотя и может, но практически не встречается в программах на C-подмножестве, т.к. и он служит в
C++ для поддержки специальных задач при работе с полиморфными объектами. Для его работы необ-
ходимо включить опцию /GR+ поддержки компилятором RTTI (Run Time Type Information).

//Listing 1.13. Новые типы и операторы (компилировать с опцией /GR+)


#include <iostream>
#include <typeinfo>
using namespace std;
void demo_types()
{
bool c = false, cpp = false;

if(sizeof('A') == sizeof(char))
cpp = true;
else
c = true;

struct compiler
{
bool c_mode;
bool cpp_mode;
};
compiler comp = {c,cpp}; //без struct и typedef
compiler *pcomp = &comp; //указатель на структуру compiler

cout << typeid(comp).name() << endl; //имя типа объекта comp

bool compiler::*mp //mp – указатель на член структуры


= &compiler::c_mode; //бинарный :: - доступ к контексту
//имен структуры
cout << "comp.c_mode = "
<< comp.*mp << endl; //.* - оператор разадресации

mp = &compiler::cpp_mode;
cout << "comp.cpp_mode= "
<< pcomp ->*mp << endl; //->* - оператор разадресации
}

Центр «Специалист» www.specialist.ru


18 Программирование на Visual C++

Задачи
1.1. Разработайте структуры Date и Time и (перегруженные) функции Set, Print и Read для уста-
новки, печати и чтения с клавиатуры значений даты и времени. Предусмотрите возможность для уста-
новки времени нулевого значения секунд по умолчанию. Предусмотрите в функциях Set присваивание
компонентам даты и времени только допустимых значений. Протестируйте их работу.

1.2*. В дополнение к задаче 1.1 разработайте (перегруженные) функции Equal, Less и Grater для
выполнения сравнений «равно», «меньше» и «больше» значений даты и времени. Протестируйте их
работу.

1.3. Разработайте функции Create,Resize и Remove для создания, изменения размера и удаления
массива целых чисел в динамической памяти. Не используйте функции стандартной библиотеки языка
C, примените операторы new и delete. Протестируйте Вашу реализацию поддержки динамических
массивов.
*
1.4 . Реализуйте обобщенные (при помощи typedef) версии функций задачи 1.3 для поддержки дина-
мических массивов произвольных типов. Протестируйте их работу, задавая в typedef разные типы, в
том числе Date и Time.

1.5*. Разработайте реализацию односвязного списка узлов, содержащих значения целого типа, и набор
функций для поддержки различных операций со списком (вставки, удаления элементов и др.). Для
размещения узлов списка в динамической памяти не используйте функции стандартной библиотеки
языка C, примените операторы new и delete. Протестируйте Вашу реализацию поддержки списков
целых чисел.
** *
1.6 . Реализуйте обобщенные (при помощи typedef) версии структур и функций задачи 1.5 для под-
держки списков произвольных типов данных. Протестируйте их работу, задавая в typedef разные ти-
пы, в том числе Date и Time.

1.7***. Реализуйте для задач 1.4* и 1.6* версии функции Apply, которая «обходит» все элементы контей-
нера и применяет к каждому элементу заданную функцию – свой параметр, например, Print. Протес-
тируйте их работу, в том числе с типами Date и Time.

Вопросы для обсуждения и «углубления»


1.1. Как можно интерпретировать фразу из вышеприведенного определения «ссылка … внутренне реа-
лизована как «саморазадресуемый» указатель»?

1.2. Ссылки в качестве аргументов функции являются альтернативой унаследованному из языка C спо-
собу «передачи по указателю»:

void swap(int &a, int &b);


void swap(int *a, int *b);

Сравните эти два механизма по разным критериям: эффективности, безопасности, гибкости и др. В ка-
ких ситуациях предпочтителен тот или иной способ?

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 19

1.3. Какой смысл имеют (и имеют ли вообще) для функции max из листинга 1.3 выражения
max(x,y) = 0;
++max(x,y);

1.4. Рассмотрите модификацию листинга 1.4

//Listing 1.4a
#include <iostream>
double a = 12.345;
void demo_scope()
{
int a = 100;
{
char a = 'A';
std::cout << " a = " << a << std::endl;
std::cout << "::a = " << ::a << std::endl;
}
}

Что будет напечатано в этом случае? Что изменится при удалении строки
double a = 12.345;?

1.5. Операторы new и delete являются альтернативой стандартным функциям языка С


malloc(),calloc() и free().Сравните эти два механизма работы с динамической памятью по раз-
ным критериям: эффективности, безопасности, гибкости и др. Почему в C++ нет оператора аналогично-
го функции realloc()?

1.6. Встраиваемые (inline) функции являются альтернативой унаследованным из языка С директи-


вам препроцессора #define(), например:

inline int min(int a, int b)


{
return a < b ? a : b;
}

#define MIN(a,b) ( (a)<(b) ? (a) : (b) )

Сравните эти два механизма по разным критериям: эффективности, безопасности, гибкости и др. По-
чему в C++ не рекомендуется использовать #define()?

В каких ситуациях и почему бесконтрольное использование inline-функций как средства оптимиза-


ции кода по быстродействию, как впрочем, и #define(), вместо ускорения может привести даже к за-
медлению исполнения программы?
Какие средства языка C++ рекомендуется использовать вместо #define без параметров и почему?

1.7. Являются ли перегруженными функции


int f(int);
int f(int &);
int f(const int);
int f(const int &);

при вызове их с параметрами


Центр «Специалист» www.specialist.ru
20 Программирование на Visual C++

int a = 1;
const int b = 2;

Являются ли перегруженными функции

int g(int);
int g(int, int =0);
int g(int, int, int =0);

при вызове их с параметрами

int a = 1, b = 0, c = 2;
g(a);
g(a, b);
g(a, b, c);

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

1.9. Эквивалентны ли по своим последствиям инструкции

cout << "Hello, world!\n";


cout << "Hello, world!" << endl;

1.10. Для какой цели в C++ был введен тип данных bool, ведь язык C обходился без него?

1.11. В C++ существуют «неименованные», или «безымянные», объединения (union). Какое примене-
ние они находят в C++?

1.12. Язык C++ разрабатывался «максимально близким к C, но не ближе, чем это необходимо». Какие
«тонкие» различия есть между ANSI C и C-подмножеством C++?

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 21

Модуль 2. Инкапсуляция

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

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

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

Класс — ключевое понятие парадигмы объектно-ориентированного программирования (ООП). Именно


через классы в C++ реализуются основные механизмы ООП: инкапсуляция, наследование и динамиче-
ский полиморфизм.

Переменная типа «класс» носит название «объект» или «экземпляр класса».

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

class идентификатор

определение_компонентов
};

Опережающее объявление класса:

class идентификатор;

Спецификаторы доступа к компонентам класса


Права доступа к компонентам класса регулируются спецификаторами доступа:

public — открытые (общедоступные) компоненты;

private — закрытые компоненты (доступные в методах класса);

protected — защищенные компоненты (имеют значение при наследовании классов).

При помощи спецификаторов доступа классы реализуют механизм инкапсуляции.

Центр «Специалист» www.specialist.ru


22 Программирование на Visual C++

Пример – класс Point (начало)


Программа с классами в C++ обычно состоит из нескольких файлов:

1. Определение класса находится в заголовочном файле .h, имя которого совпадает с именем опреде-
ляемого класса, например, Point.h:

//Listing 2.1. Файл Point.h: определение класса Point


#ifndef POINT_H
#define POINT_H

class Point //заголовок описания класса Point


{ //начало закрытой (по умолчанию) части класса
private: //спецификатор закрытой части (секции) класса

int x_, y_; //члены-данные (свойства) класса (подчеркивание в


//конце – это современные рекомендации экспертов)
public: //спецификатор открытой части - интерфейса класса
//** 1 ** минимальный интерфейс класса
//объявленные ниже методы доступа (методы-аксессоры)
// Get...() и Set...() образуют минимальный интерфейс класса
int GetX() //член-функция (метод) для чтения компонента х_
{ //определение метода в классе неявно объявляет его
return x_; // встроенным (inline) (лучше этого избегать)
}

int GetY(); //это прототип метода, а его определение находится


//здесь же, но после описания класса (так лучше)
void SetX(int); //метод для установки значения компонента х_,
//определение его и всех остальных методов
void SetY(int); //размещается в файле реализации Point.cpp
};
//Определение встроенной функции должно быть видимым компилятору в точке
//ее вызова, по этой причине оно расположено в заголовочном файле.
//Имена функций-членов, определяемых вне контекста класса, должны быть
//полностью квалифицированы (уточнены) при помощи бинарного оператора ::
inline int Point::GetY() //здесь inline надо объявлять явно
{
return y_;
}

#endif

2. Код реализации методов класса располагается в файле .cpp, имя которого также совпадает с именем
определяемого класса, например, Point.cpp:

//Listing 2.2. Файл Point.cpp: реализация класса Point


#include "Point.h" //включение делает видимым определение
//класса Point в файле реализации

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 23

//В файле реализации имя каждой функции-члена класса должно быть


//полностью квалифицировано при помощи бинарного оператора ::
void Point::SetX(int x)
{
x_ = x >= 0 ? x : 0;
}
void Point::SetY(int y)
{
y_ = y >= 0 ? y : 0;
}

Примечание. На функции Set...() часто возлагают контроль над соблюдением


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

3. Код, использующий класс (клиент класса, клиентский код), размещается в отдельном файле .cpp, имя
которого произвольно, но часто совпадает с именем используемого класса с префиксом use, например,
usePoint.cpp (см. ниже).

Определение экземпляров класса (объектов)


Простейшее определение экземпляра (объекта):

класс идентификатор;

Определение указателя на экземпляр (объект) класса:

класс *идентификатор;

Определение ссылки на экземпляр (объект) класса:

класс &идентификатор;

Доступ к компонентам класса.


Доступ к открытым компонентам объекта через объект класса:

объект.компонент

или с использованием квалифицированного имени:

объект.класс::компонент

Доступ через указатель на объект класса:

указатель->компонент

или с использованием квалифицированного имени:

указатель->класс::компонент

Центр «Специалист» www.specialist.ru


24 Программирование на Visual C++

//Listing 2.3. Файл usePoint.cpp: клиентский код, использующий Point

#include <iostream>
#include <cmath>

#include "Point.h"
using namespace std;

//клиентская функция для измерения расстояния между точками

double gdist(Point &a, Point &b) //передача по ссылке


{ //использует Get...() для доступа к x_ и y_
return sqrt(pow(static_cast<double>(a.GetX()-b.GetX()),2)
+pow(static_cast<double>(a.GetY()-b.GetY()),2));
}

int main()
{

//** 1 **использование минимального интерфейса


Point a; //Создание экземпляра класса Point в стеке
// a.x_ = 6; //ERROR, т.к. x_ объявлен private
// a.y_ = 8; //ERROR, т.к. y_ объявлен private

a.SetX(6);
a.SetY(8);
cout << a.GetX() << ',' << a.GetY() << endl;
Point *p = new Point; //Создание экземпляра класса Point в куче
p->SetX(9);
p->SetY(12);
cout << p->GetX() << ',' << p->GetY() << endl;
cout << "Distance = "
<< gdist(a,*p) //разадресация, т.к. требуется Point
<< endl;

delete p; //Удаление из кучи (нельзя забывать!)


}

Указатель this
Указатель this — константный указатель на объект класса, который неявно определен в каждой не-
статической функции–члене класса и при ее вызове инициализируется адресом объекта, для которого
вызвана функция. Адрес передается как дополнительный (скрытый) параметр функции-члена. Опре-
делить или описать указатель this явным образом невозможно.

Примечание. Указатель this имеет в C++ специальное применение и для рассматриваемого класса
Point его использование не является оправданным. Но, в учебных целях, ниже в реализации функции
Move() показано, где в коде метода класса компилятор «скрыто вписывает» this. В последующих
разделах мы увидим, когда применение указателя this является действительно необходимым.
Центр «Специалист» www.specialist.ru
Программирование на Visual C++ 25

Дружественные функции
Дружественная функция — функция, которая не является членом класса, но имеет доступ ко всем его
компонентам, в том числе закрытым и защищенным.

Особенности дружественных функций в языке C++:

– при вызове не получают указателя this;

– на них не действуют спецификаторы доступа того класса, по отношению к которому они являются
дружественными;

– дружественными по отношению к данному классу могут быть как свободные (не принадлежащие ни-
какому классу) функции, так и функции-члены других классов;

– могут быть дружественными по отношению к нескольким классам.

Объявление дружественной функции в классе:

friend тип идентификатор(формальные_аргументы);

При определении дружественной функции (вне класса) спецификатор friend не пишется.

Дружественные классы
Дружественный класс — это класс, все функции-члены которого являются дружественными по отноше-
нию к другому классу.

Объявление дружественного класса:

friend class класс;

Пример – класс Point (продолжение)


Описанный выше класс Point предоставляет клиентам этого класса минимальный интерфейс, посред-
ством которого клиенты могут выполнять любые необходимые им манипуляции с объектами, но не са-
мым удобным способом.

Например, печать координат в листинге 2.3 выглядит достаточно громоздкой, еще более сложной ма-
нипуляцией будет смещение точки, скажем, на 10 пикселей по горизонтали и на 20 – по вертикали (на-
пишите этот код).

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

По этой причине разработчик класса должен спроектировать и предоставить клиенту класса более «бо-
гатый» интерфейс для расширения функциональности класса и повышения удобства работы с его объ-
ектами.
Центр «Специалист» www.specialist.ru
26 Программирование на Visual C++

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

//Listing 2.1a. Файл Point.h: расширение класса Point


// ...
class Point
{
private:
// ...

public:
//** 1 ** минимальный интерфейс класса
//...
//** 2 ** методы, расширяющие функциональность класса

void Print(); //печать координат точки


//в формате (x,y)
bool Read(const char * =0); //ввод с клавиатуры
//(с подсказкой)
void Move(int delta_x, int delta_y); //относительное перемещение
//(от текущей позиции)
double Dist(const Point &); //расстояние между точками
//(имеет один аргумент!)
//** 3 ** внешняя функция, объявленная дружественной к классу
friend double fdist(const Point &, const Point &); //расстояние
};
// ...
//Listing 2.2a. Файл Point.cpp: реализация расширений класса Point
#include <iostream>
#include <cmath>
#include "Point.h"

using namespace std;


// ...
void Point::Print()
{
cout << '(' << x_ << ',' << y_ << ')';
}
bool Point::Read(const char *txt)
{
if(txt) cout << txt;
int x, y;
char c;
if( cin >> c && c == '(' &&
cin >> x &&
cin >> c && c == ',' &&
cin >> y &&
cin >> c && c == ')'
)
Центр «Специалист» www.specialist.ru
Программирование на Visual C++ 27

{
SetX(x); SetY(y);
return true;
}
if(!cin) cin.clear();
while(cin.get() != '\n');
return false;
}
//вот как «на самом деле» выглядит метод Move (и другие методы тоже!)
void Point::Move(int dx, int dy)
{
this->SetX(this->x_ + dx); //методы класса могут использовать
this->SetY(this->y_ + dy); //другие методы своего класса
}
double Point::Dist(const Point &other)
{
return sqrt( pow(static_cast<double>(x_-other.x_),2)
+ pow(static_cast<double>(y_-other.y_),2)
);
}
//** 3 **
//реализацию friend-функции целесообразно размещать в файле
//реализации класса, потому что она тесно связана с классом;
//она НЕ использует GetX() и GetY() так как имеет непосредственный
//доступ к компонентам x_ и y_
double fdist(const Point &a, const Point &b)
{
return sqrt( pow(static_cast<double>(a.x_-b.x_),2)
+ pow(static_cast<double>(a.y_-b.y_),2)
);
}

a
//Listing 2.3 . Файл usePoint.cpp: клиент, использующий расширения Point

// ...

int main()
{
// ...
//** 1 ** использование минимального интерфейса
Point a;
// ...
Point *p = new Point;
// ...
//** 2 ** использование расширенного интерфейса
a.Move(-10,4);
p->Read("Enter Point in the form(x,y) ");

cout << "Distance from "; a.Print();


cout << " to "; p->Print();
cout << " is " << p->Dist(a) << endl; //или a.Dist(*p)

Центр «Специалист» www.specialist.ru


28 Программирование на Visual C++

//** 3 ** вызов friend-функции

cout << "Distance = " << fdist(a,*p) << endl;

delete p;
}

Задачи
2.1. Разработайте классы Date и Time для работы с датами и временем. Реализуйте для них полезные
с Вашей точки зрения методы. Используйте результаты решения задач 1.1. и 1.2 *.

2.2. Используя класс Point, разработайте класс Pixel (т.е. точка Point, имеющая цвет). Реализуйте
методы Get…, Set…, Print, Read, Move, Dist.

2.3. Разработайте класс Line (отрезок на плоскости), заданный его конечными точками (используйте
класс Point). Реализуйте методы: Get…, Set…, Print, Read, Move, Length, Center.

2.4*. Если в задаче 2.3. реализовать метод Line::Move() посредством Point::Move(), то при
больших отрицательных перемещениях отрезок будет деформироваться. Разработайте метод
Line::Move() так, чтобы отрезок не деформировался, но и не выходил в область отрицательных ко-
ординат.

2.5*. Разработайте функцию, которая определяет, принадлежит ли точка Point отрезку Line. К какому
из классов Вы отнесете эту функцию или оставите ее свободной?

2.6. Разработайте класс Triangle (треугольник), заданный на плоскости координатами его вершин.
Реализуйте методы: Get…, Set…, Print, Read, Move, Perimeter, Area, Center.
* * *
2.7 . Рассмотрите условия задач 2.4 . и 2.5 . применительно к классу Triangle.

2.8**. Для класса Pixel из задачи 2.2. реализуйте методы Draw (нарисовать) и Erase (стереть) и вы-
полните их визуализацию в консольном режиме.

2.9*. Разработайте класс Timer, который через установленный интервал времени вызывает заданную
функцию.

2.10***. Разработайте класс TrafficLights (светофор). Визуализируйте его работу. Используйте ре-
зультаты решения задач 2.8*. и 2.9*.

Вопросы для обсуждения и «углубления»


2.1. В C++ определение класса может вводиться ключевыми словами class или struct. Сравните эти
два способа. В каких случаях тот или иной способ предпочтительнее использовать?

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 29

2.2. Когда в C++ требуется полное определение класса, и в каких ситуациях можно или нужно исполь-
зовать опережающее объявление?

2.3. Члены-данные (поля) класса следует определять как private компоненты. Объясните смысл этой
рекомендации и приведите хотя бы два/три довода в ее пользу. А если на значения полей нет ограни-
чений, то какой смысл писать «примитивные» методы Get и Set?

2.4. Кроме традиции именования методов доступа как Get и Set, существует и альтернатива – исполь-
зовать перегрузку. Так для класса Pixel можно создать методы:

Color color(); – аналог Color GetColor();


void color(Color); – аналог void SetColor(Color);
2.5. Почему в C++ определение класса обычно располагается в отдельном заголовочном файле? Какие
ситуации являются исключениями из этого общего правила?

2.6. Допускается ли в классе иметь несколько private, public и protected секций?

2.7. В какой последовательности в определении класса можно и/или нужно располагать private, pub-
lic и protected секции?

2.8. По какой причине во всех вариантах функций, измеряющих расстояние между точками: gdist(),
fdist() и Point::Dist(), используется оператор явного приведения типов static_cast<>()?

2.9. По какой причине в функцию gdist() аргументы передаются по «простой» ссылке, а в функции
fdist() и Point::Dist() – по ссылке «на константу»?

2.10. Какой смысл в листинге функции Point::Read() (Listing 2.2a.) имеют в цепочке конъюнкций про-
верки if( cin >> c && … && cin >> x && … && cin >> y && …)?

2.11. Какую роль в листинге функции Point::Read() (Listing 2.2a.) выполняют инструкции
if(!cin) cin.clear();
while(cin.get() != '\n');?

2.12. Если в классе объявляется дружественная функция, которой разрешен доступ ко всем компонен-
там, в том числе и к private, не нарушает ли это инкапсуляцию класса?

2.13. Как много методов должен разработать проектировщик класса, чтобы клиентам класса было бы «и
легко и удобно» манипулировать объектами класса?

2.14. Сформулируйте определение, что такое «интерфейс класса».

2.15. В дополнение к п.2.1., класс в C++ может определяться и при помощи ключевого слова union. Ка-
кими свойствами обладают подобные классы?

Центр «Специалист» www.specialist.ru


30 Программирование на Visual C++

Модуль 3. Специальные методы класса

Понятие конструктора класса


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

Код вызова конструктора каждый раз (неявно) включается компилятором в то место программы, где ка-
ким-либо способом создается объект класса: как изолированный объект, как элемент массива или
часть объекта другого класса – и «обойти» этот код вызова невозможно. Так же не существует синтак-
сической конструкции явного вызова кода конструктора.

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

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

Автоматически генерируемый конструктор по умолчанию (неявный конструктор) имеет следующий


вид:

класс::класс() { }

но в «пустом» теле может находиться «скрытый» код.

Конструктор копирования имеет прототип:

класс::класс(const класс &);

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

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


сигнатуру (набор аргументов). При этом, если разработчик сам создал хотя бы один из них – конструк-
тор по умолчанию автоматически сформирован не будет.

Среди прочих конструкторов особую роль играют конструкторы преобразования, которые (явно)
принимают лишь один параметр, причем его тип отличен от класс. Такие конструкторы выступают в
роли кандидатов для неявных приведений от типа аргумента к типу класс. Если же для неявного при-
ведения требуется «цепочка» преобразований, то в ней будет использован только один конструктор.
Для запрета неявного вызова конструктора преобразования используется ключевое слово explicit в
его объявлении.

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 31

Инициализация объекта в конструкторе


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

Список инициализации – это совокупность разделенных запятыми пар

поле(выражение),

расположенных между заголовком и телом конструктора и отделенных от заголовка двоеточием:

класс::класс(формальные_аргументы)
:список_инициализации

{ тело_конструктора }

Вызов конструкторов
При «обычном» создании объектов инструкциями вида:

класс идентификатор;
класс *идентификатор = new класс;

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

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

класс идентификатор(фактические_параметры);
класс идентификатор = класс(фактические_параметры);

а в случае конструктора с одним аргументом (конструктора преобразования или конструктора копиро-


вания) возможна и такая запись:

класс идентификатор = фактический_параметр;

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

класс(фактические_параметры)

Центр «Специалист» www.specialist.ru


32 Программирование на Visual C++

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


Деструктор – это специальный метод класса, который предоставляет механизм «очистки» уничтожае-
мых объектов.

Код вызова деструктора каждый раз (неявно) включается компилятором в то место программы, где ис-
текает время жизни объекта и освобождается занимаемая им область памяти, в том числе, как часть
работы оператора delete, и «обойти» этот код вызова невозможно.

Имя деструктора состоит из имени класса, которому предшествует символ «тильда» (~). Деструктор не
имеет аргументов и для него не указывается тип возвращаемого значения (в том числе void):

~класс();

Если программист явно не объявил и не определил деструктор класса, то автоматически генерируемый


компилятором деструктор будет иметь следующий вид:

класс::~класс(){ }

но в «пустом» теле может находиться «скрытый» код.

Допускается явный вызов деструктора по имени, как любой другой функции-члена класса, например:

объект.~класс();

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

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

Прототип оператора присваивания обычно имеет вид:

класс &operator =(const класс &);

а его код выполняет покомпонентное копирование всех полей объекта.

Пример – класс Point (продолжение)


Ниже для класса Point (см. листинги 2.1a – 2.3a) реализованы все его специальные методы, причем
для конструктора копирования, деструктора и оператора присваивания «визуализирован» код, автома-
тически генерируемый в них компилятором. Во все методы дополнительно включена отладочная пе-
чать, чтобы иметь возможность наглядно видеть их работу.

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 33

//Listing 3.1. Файл Point.h: объявления специальных методов класса


// ...
class Point
{
private:
// ...

public:
//** 1 ** минимальный интерфейс класса
// ...
//** 2 ** методы, расширяющие функциональность класса
// ...
//** 3 ** внешняя функция, объявленная дружественной к классу
// ...
//** 4 ** конструкторы и деструктор (специальные методы) класса
Point(); //конструктор по умолчанию

/*explicit*/ Point(int); //конструктор преобразования


Point(int,int); //явный конструктор
Point(const Point &); //конструктор копирования
~Point(); //деструктор класса

//** 5 ** оператор присваивания


Point &operator =(const Point &);

};
// ...

//Listing 3.2. Файл Point.cpp: реализация специальных методов класса


// ...

Point::Point()
{
SetX(0);
SetY(0);
//для отладки
cout << "Point(): "; Print(); cout << endl;
}

Point::Point(int x)
{
SetX(x);
SetY(0);

//для отладки
cout << "Point(int): "; Print(); cout << endl;
}

Центр «Специалист» www.specialist.ru


34 Программирование на Visual C++

Point::Point(int x, int y)
{
SetX(x);
SetY(y);

//для отладки
cout << "Point(int,int): "; Print(); cout << endl;
}
Point::Point(const Point &other)
: x_(other.x_), y_(other.y_) //список инициализации
{
//для отладки
cout << "Point(const Point &): "; Print(); cout << endl;
}

Point::~Point()
{
//для отладки
cout << "~Point(): "; Print(); cout << endl;
}
Point &Point::operator =(const Point &other)
{
x_ = other.x_;
y_ = other.y_;
//для отладки
cout << "operator=(const Point &): "; Print(); cout << endl;
return *this; //возвращаем левый операнд
//вот где явно используется this (см. модуль 2)
}

//Listing 3.3. Файл usePoint.cpp: использование специальных методов //класса

// ...
int main()
{
//** 1 ** использование минимального интерфейса
Point a;
// ...
Point *p = new Point;
// ...
//** 2 ** использование расширенного интерфейса
// ...
//** 3 ** вызов friend-функции
// ...
delete p;
//** 4 ** конструирование объектов конструкторами с параметрами
Point b1(10,20);
Point b2 = Point(11,21);
Point *p1 = new Point(12,22);

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 35

Point c1(20);
Point c2 = Point(21);
Point c3 = 22;
Point *p2 = new Point(23);
Point d1(b1);
Point d2 = Point(b1);
Point d3 = b1;
Point *p3 = new Point(b1);
delete p1;
delete p2;
delete p3;

//** 5 ** присваивание
d3 = b1; //эквивалентно d3.operator=(b1);
//** 6** приведение типов аргументов
d3 = 777;
d3 = 7.77;
d3 ='7';
// d3 = "7"; - некомпилируется
}

Примечание. В этом месте Вам рекомендуется выполнить задачи 3.1 – 3.2*.

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

Пример – класс String (начало)


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

Примечание. Этот класс создается лишь в учебных целях, чтобы на «понятном» примере лучше разо-
браться с теми проблемами, которые характерны для классов, обладающих системными ресурсами, но
в реальных проектах следует использовать стандартный класс std::string.

Указание. Возможно, Вам придется назвать свой класс иначе, например MyString, чтобы избежать
конфликта имен при работе в среде Visual C++ 2010 или более поздних версиях.

//Listing 3.4. Файл String.h – определение класса String


#ifndef STRING_H
#define STRING_H

class String
{
public:
Центр «Специалист» www.specialist.ru
36 Программирование на Visual C++

int Length(){ return n_; }


void Print(){ std::cout << s_; }

String(const char *str ="");


~String();
String(const String &);
String &operator =(const String &);
private:
size_t n_; //длина строки (без учета завершающего „\0‟)
char *s_; //адрес буфера для строки в динамической памяти
//память выделяется в конструкторе
//ее следует освободить в деструкторе
//также для класса следует переопределить
//конструктор копирования и оператор присваивания
};
#endif

//Listing 3.5. Файл String.cpp – реализация класса String


#include <iostream>
#include <cstring>
#include "String.h"
using namespace std;

String::String(const char *str)


{
n_ = strlen(str); //вычислили длину строки
s_ = new char[n_+1]; //выделили буфер
//здесь может быть отказ в выделении памяти
strcpy(s_,str); //скопировали в буфер строку
}

String::~String()
{
delete[] s_; // если new[], то и delete[]
}

String::String(const String &str) //глубокая копия


{
n_ = str.n_; //извлеклили длину строки
s_ = new char[n_+1];
//здесь может быть отказ в выделении памяти
strcpy(s_, str.s_);
}

String & String::operator =(const String &str)


{
if(this == &str) return *this; //защита от самоприсваивания
delete[] s_; //освободили блок, которым владели
n_ = str.n_;
s_ = new char[n_+1];
//здесь может быть отказ в выделении памяти
strcpy(s_, str.s_);
return *this; //возвращаем левый операнд
}
Центр «Специалист» www.specialist.ru
Программирование на Visual C++ 37

Примечание. Для отключения предупреждений компилятора о возможной «опасности» ряда функций


(strcpy, strcat и др.) поместите перед всеми директивами #include директиву #define
_CRT_SECURE_NO_WARNINGS

Примечание. Хотя C++ «не приветствует» использование в программном коде директивы препроцессо-
ра #define,но и «не препятствует» ее применению в целях отладочной печати. Ниже в листинге де-
монстрируется прием включения и выключения отладочной печати при помощи директив условной
компиляции и директив #define

//Listing 3.6. Файл useString.cpp – клиент класса String

#include <iostream>
#include "String.h"

using namespace std;

#define MYDEBUG
#ifdef MYDEBUG
#define DUMP(a) cout << "String " #a " = \""; (a).Print(); \
cout << "\", Length = " << (a).Length() << endl
#else
#define DUMP(a)
#endif

int main()
{
String a; //конструктор умолчания
DUMP(a);
String b = "Hello"; //конструктор преобразования
DUMP(b);

String *p = new String("Good bye");


DUMP(*p);
delete p;

String c = b; //конструктор копии


DUMP(c);
a = b; //присваивание: a.operator=(b)
DUMP(a);

a = a; // самоприсваивание
DUMP(a);
b = a = "Wellcome!"; //«цепь» операторов присваивания
DUMP(a);DUMP(b);
}

Центр «Специалист» www.specialist.ru


38 Программирование на Visual C++

Задачи
3.1. Для классов Pixel и Line (задачи 2.2 и 2.3) реализуйте необходимые и полезные с Вашей точки
зрения специальные методы класса. А какие методы Вы «доверите» сгенерировать компилятору?
*
3.2 . Для классов Date и Time (задача 2.1) выполните требования задачи 3.1. и рассмотрите возмож-
ность использования в реализации этих классов средств стандартной библиотеки (см. файл <ctime>).
Какие еще полезные методы Вы сочтете возможным реализовать?

3.3**. Используя классы Date, Time и String, создайте класс «Ежедневник» (Organizer), состоя-
щий из записей типа «Событие» (класс Event) с полями «дата», «время», «описание события», «от-
метка об исполнении». Для поддержки списка записей примените результат решения задачи 1.4 *.
Реализуйте печать списка событий, запланированных на указанную дату (значение по умолчанию – се-
годня), а также списков событий за указанный диапазон дат, отдельно – выполненных и невыполнен-
***
ных. Используйте результат решения задачи 1.7 .
** ** **
3.4 . В задаче 3.3 для списка записей примените результат решения задачи 1.6 .

3.5. Используйте материал этого модуля для дальнейшей «проработки» класса TrafficLights (зада-
ча 2.10***).

Вопросы для обсуждения и «углубления»


3.1. В классе Point удалите (или закомментируйте) объявление и определение конструктора по умол-
чанию. Откомпилируйте код и объясните получившийся результат.
Восстановите исходный код класса.

3.2. В определении класса Point удалите символы комментария в строке

/*explicit*/ Point(int);

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


Восстановите исходный код класса.

3.3. В конце функции main листинга 3.3 для каждой строки в разделе //** 6 ** напишите эквива-
лентный код вызова подобно тому, что написан в разделе //** 5 **.

3.4. В классе Point удалите (или закомментируйте) объявления и определения трех конструкторов

Point();
/*explicit*/ Point(int);
Point(int,int);

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

Point(int =0,int =0);

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


Добавьте объявление explicit, откомпилируйте код и объясните получившийся результат.

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 39

Сравните эти два способа. В каких случаях тот или иной способ предпочтительнее использовать?
Восстановите исходный код класса.

3.5. В классе String удалите (или закомментируйте) объявление и определение конструктора копиро-
вания и оператора присваивания. Откомпилируйте код и объясните получившийся результат.
Последовательно восстанавливайте в классе конструктор копирования, затем оператор присваивания
(но вначале без первой строки, содержащей инструкцию if), каждый раз компилируйте код и объясняй-
те получившийся результат.

3.6. Объясните, как работает «цепь» операторов присваивания в конце функции main в листинге 3.6,
для чего напишите эквивалентный код вызова (см. вопрос 3.3).

3.7. Почему конструкторы и деструктор класса обычно являются «неудачными» кандидатами в inline-
функции?

3.8. Деструктор – это специальный метод класса, который предоставляет механизм «очистки» уничто-
жаемых объектов. Что в этом утверждении понимается под «очисткой» объекта?

3.9*. Явный вызов деструктора применяется лишь при решении специальных задач управления разме-
щением объектов в памяти. О каких специальных задачах здесь упоминается?

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

3.11. В каких случаях разработчик класса «не может», «не должен» или «может не хотеть» доверить
компилятору автоматическую генерацию специальных методов класса?

3.12*. Прототип оператора присваивания обычно имеет вид

имя_класса &operator =(const имя_класса &);

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

3.13*. В классе Point конструктор копии реализован при помощи списка инициализации

Point::Point(const Point &other)


: x_(other.x_), y_(other.y_) //список инициализации
{}

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

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

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

Центр «Специалист» www.specialist.ru


40 Программирование на Visual C++

3.16*. В конец функции main в листинге 3.3 вставьте фрагмент кода

//** 7 ** конструирование массивов объектов


Point arr[5] = {b1, Point(1,2), Point(3), 4};

Point *parr = new Point[3];


for(int i=0; i<3; ++i)
parr[i] = arr[i];

delete[] parr;

Изучите и объясните результат его работы. В этом примере показан способ инициализации массива с
классом памяти auto при помощи различных конструкторов. А какие способы инициализации сущест-
вуют для динамического массива?
Исследуйте и объясните, что происходит, если в операторе delete «потерять» []?

3.17*. Перед функцией main в листинге 3.3 вставьте определения двух функций

Point test1(Point pt)


{
cout<<"--- Point test1(Point): "<<endl;
// ...
return pt;
}
Point &test2(Point &pt)
{
cout<<"--- Point &test2(Point &): "<<endl;
// ...
return pt;
}

а в ее конец – фрагмент кода

//** 8 ** сравнение механизмов передачи аргументов и возврата значений


cout << "--------------------" << endl;
a = test1(b1);
cout << "--------------------" << endl;
a = test2(b1);
cout << "--------------------" << endl;

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

3.18*. В конец функции main в листинге 3.3 вставьте фрагмент кода

//** 9 ** передача аргументов и приведение типов


cout << "--------------------" << endl;
a = test1(b1);
cout << "--------------------" << endl;
a = test1(777);
cout << "--------------------" << endl;
a = test1(7.77);
cout << "--------------------" << endl;
a = test1('7');
cout << "--------------------" << endl;
Центр «Специалист» www.specialist.ru
Программирование на Visual C++ 41

// a = test1("7"); - не компилируется

cout << "--------------------" << endl;


a = test2(b1);
cout << "--------------------" << endl;
a = test2(777);
cout << "--------------------" << endl;
a = test2(7.77);
cout << "--------------------" << endl;
a = test2('7');
cout << "--------------------" << endl;
// a =test2("7"); - некомпилируется

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


вить работоспособность программы.

3.19*. Перед функцией main в листинге 3.3 вставьте определение функции

const Point &test3(const Point &pt)


{
cout << "--- const Point & test3(const Point &): " << endl;
// ...
return pt;
}

а в ее конец – фрагмент кода

cout << "--------------------"<<endl;


a = test3(b1);
cout << "--------------------"<<endl;
a = test3(777);
cout << "--------------------"<<endl;
a = test3(7.77);
cout << "--------------------"<<endl;
a = test3('7');
cout << "--------------------"<<endl;
// a = test3("7"); - некомпилируется
Изучите и объясните результат его работы. Сравните все рассмотренные механизмы передачи пара-
метров в функцию и возврата значения и предложите рекомендации, когда какой способ следует при-
менять.
**
3.20 . Компилятор выполняет так называемую «оптимизацию передачи параметров в функцию». На
примере сравнения двух вызовов функции test1 в листинге 3.3

a = test1(b1);
a = test1(777);

объясните суть механизма оптимизации.

3.21**. Компилятор выполняет и так называемую «оптимизацию возврата значения из функции». На


примере метода Center класса Line в задаче 3.1 изучите возможность и добейтесь оптимизации
возврата значения (если уже не сделали этого ранее).

3.22***. Рассмотрите альтернативные реализации методов класса String (сравните с листингом 3.5) и
выскажите Ваши суждения на этот счет.

Центр «Специалист» www.specialist.ru


42 Программирование на Visual C++

String::String(const char *str)


{
n_ = strlen(str); //вычислили длину строки
s_ = strcpy(new char[n_+1],str); //выделили память и скопировали
}
String::String(const String &str)
: n_(str.n_), s_(strcpy(new char[str.n_+1],str.s_))
{
}

String &String::operator =(String str)


{
swap(n_, str.n_); //функция стандартной библиотеки,
swap(s_, str.s_); //выполняющая обмен значениями
return *this;
}
Можно ли и для первого конструктора корректно воспользоваться списком инициализации?
**
3.23 . Конструктор копии, реализованный для класса String (листинг 3.5), выполняет так называемое
«глубокое» копирование, в отличие от «поверхностного» копирования, которое выполнял бы автомати-
чески сгенерированный компилятором конструктор копии. Какие еще реализации метода копирования
Вы могли бы предложить, и в каких задачах они могли бы применяться? Рассмотрите в качестве аль-
тернативы глубокому копированию метод копирования с подсчетом ссылок.
** **
3.24. При переходе от решения задачи 3.3 к задаче 3.4 много ли изменений Вам пришлось внести в
клиентский код?
Если да, то у Вас недостаточная степень инкапсуляции данных в классе, если клиентский код не изме-
нился – то все отлично!
Одним из важнейших критериев «хорошо» спроектированного класса является независимость клиентов
класса от способа представления данных в классе.

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 43

Модуль 4. Перегрузка операторов (операций)

Перегрузка операторов
Перегрузка операторов — это расширение действия стандартных операторов, определенных в языке
для встроенных типов, на операнды типов «класс».

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

перегрузка возможна только в том случае, если хотя бы один из операндов имеет тип «класс»

перегружать можно только существующие операторы и нельзя «изобрести» новые лексемы опе-
раторов

перегрузка не может изменить приоритет и ассоциативность операторов

перегрузка допускается только для унарных и бинарных операторов и не может изменить их «ар-
ности» (количества операндов)

не разрешена перегрузка операторов:

. .* ?: :: sizeof typeid() static_cast<>() const_cast<>()


dynamic_cast<>() reinterpret_cast<>()

и операторов препроцессора

# ##

Перегрузка оператора реализуется специальной операторной функцией, имеющей особое имя

operator знак_оператора

одним из двух способов:

как функция-член класса

тип класс::operator знак_оператора(формальные_аргументы);

как свободная функция

тип operator знак_оператора(формальные_аргументы);

Как член класса, операторная функция имеет на один аргумент меньше, чем предусмотрено «арно-
стью» оператора. Для унарного оператора функция принадлежит классу его единственного операнда, а
список ее аргументов пуст. Для бинарного оператора функция принадлежит классу его левого операн-
да, а ее аргумент может «принять» правый операнд, то есть иметь описание тип или ссылка_на_тип,
может быть, с квалификатором const.

Как свободная функция, для связи с классом операторная функция должна обладать хотя бы одним
аргументом класс или ссылка_на_класс, возможно, с квалификатором const, при этом она может
Центр «Специалист» www.specialist.ru
44 Программирование на Visual C++

быть, а может и не быть дружественной по отношению к классу. Свободная операторная функция име-
ет столько аргументов, сколько предусмотрено «арностью» реализуемого ею оператора: унарные –
один, бинарные – два.

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

operator =, operator [], operator ->, operator (),operator тип

Вызов операторной функции имеет две синтаксические формы:

неявную – в виде выражения, состоящего из лексемы знак_оператора и соответствующего ко-


личества операндов

знак_оператора операнд -x

операнд1 знак_оператора операнд2 x-y

явную – в виде вызова операторной функции по имени

операнд.operator знак_оператора() x.operator–()

operator знак_оператора(операнд) operator–(x)

операнд1.operator знак_оператора(операнд2) x.operator–(y)

operator знак_оператора(операнд1,операнд2) operator–(x,y)

Особенности перегрузки операторов ++ и --


Унарные операторы инкремента и декремента имеют две формы записи – префиксную и постфиксную.

Перегрузка префиксной формы операторов ++ и -- выполняется выше описанным способом.

Перегрузка постфиксной формы операторов ++ и -- реализуется включением в заголовок (прототип)


операторной функции фиктивного (обычно, неименованного) формального аргумента типа int, зани-
мающего в заголовке (прототипе) функции последнее место и имеющего при вызове нулевое значение,
например для постфиксного оператора ++:

как функция-член класса

класс класс::operator ++(int);

как свободная функция

класс operator ++(класс &, int);

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 45

Пример – класс String (продолжение)


//Listing 4.1. Файл String.h – определение класса String
// ...
class String
{
public:
// ...
String &operator +=(const String &rhs);
friend bool operator ==(const String &lhs, const String &rhs);
friend bool operator >(const String &lhs, const String &rhs);

char &operator [](int i);


private:
// ...
};
String operator +(const String &lhs, const String &rhs);

bool operator !=(const String &lhs, const String &rhs);


bool operator <=(const String &lhs, const String &rhs);
bool operator <(const String &lhs, const String &rhs);
bool operator >=(const String &lhs, const String &rhs);

// ...

//Listing 4.2. Файл String.cpp – реализация класса String

// ...
String &String::operator +=(const String &rh)
{
char *t = strcpy(new char[n_+rh.n_+1], s_);
delete[] s_;
s_ = strcat(t, rh.s_);
n_ += rh.n_;
return *this;
}

String operator +(const String &lh, const String &rh)


{
String t = lh;
return t += rh;
}

bool operator ==(const String &lh, const String &rh)


{
return strcmp(lh.s_, rh.s_) == 0;
}

Центр «Специалист» www.specialist.ru


46 Программирование на Visual C++

bool operator !=(const String &lh, const String &rh)


{
return !(lh == rh);
}

bool operator >(const String &lh, const String &rh)


{
return strcmp(lh.s_, rh.s_) > 0;
}

bool operator <=(const String &lh, const String &rh)


{
return !(lh > rh);
}

bool operator <(const String &lh, const String &rh)


{
return rh > lh;
}

bool operator >=(const String &lh, const String &rh)


{
return !(lh < rh);
}
char &String::operator [](int i)
{
if(i>=0 && static_cast<size_t>(i) < n_) return s_[i];

cerr << "Index out of range" << endl;


static char dummy;
dummy = '\0';
return dummy;
}

//Listing 4.3. Файл useString.cpp – клиент класса String

//...
int main()
{
//...
b += a;
DUMP(b);
b = c + ", World! " + a;
DUMP(b);
cout << (a==b ? "a==b" : "a!=b") << endl;
cout << (a>b ? "a>b" : "a<=b") << endl;
cout << (b>a ? "b>a" : "b<=a") << endl;
DUMP(a); DUMP(b);

a[1] = a[0];
DUMP(a);

String e;
DUMP(e);

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 47

e[1] = '#';
DUMP(e);
}

Потоковый ввод-вывод в языке C++. Общие положения


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

Поток — это последовательность байтов, связанная с устройством.

По способу организации обмена информацией потоки бывают не буферизованные и буферизованные,


причем последние бывают строчно-буферизованные или блочно-буферизованные.

По направлению обмена информацией потоки подразделяются на входные, выходные и двунаправлен-


ные.

По типам обслуживаемых устройств потоки делятся на стандартные, консольные, файловые и строко-


вые.

Потоки как механизм ввода-вывода обеспечивают независимость от файловой системы конкретной


операционной системы, контроль типов и удобный обмен данными произвольной структуры.

Важнейшие классы стандартных потоков ввода-вывода


Классы потоков в стандартной библиотеке C++ реализованы шаблонами, образующими иерархическую
структуру при помощи отношения наследования. Для удобства использования шаблонным классам при
помощи инструкции typedef присвоены псевдонимы.

Важнейшими классами (псевдонимами) являются:

ios — базовый класс потока, определяет основные свойства потока;

istream — класс входного потока;

ostream — класс выходного потока;

iostream — класс двунаправленного потока;

ifstream — класс входного файлового потока;

ofstream — класс выходного файлового потока;

fstream — класс двунаправленного файлового потока.

Кроме того, существуют потоки для обмена строками с буферами в памяти, например, istrstream,
ostrstream, strstream и другие.

Центр «Специалист» www.specialist.ru


48 Программирование на Visual C++

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

istream cin— стандартный буферизированный входной поток;

ostream cout—стандартный буферизированный выходной поток;

ostream cerr— стандартный не буферизированный выходной поток сообщений об ошибках;

ostream clog— стандартный буферизированный выходной поток сообщений об ошибках.

Операторы обмена данными и манипуляторы


Для чтения данных из потока класса istream используется перегруженный оператор >> — «оператор
извлечения из потока».

Для вывода данных в поток класса ostream используется перегруженный оператор << — «оператор
вставки в поток».

Операторы извлечения и вставки перегружены в соответствующих классах потоков для всех встроен-
ных типов языка C++.

Для проверки успешности выполнения предыдущей операции ввода-вывода в классах потоков пере-
гружен оператор bool operator !().

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

dec — при вводе-выводе устанавливает десятичную систему счисления;

hex — при вводе-выводе устанавливает шестнадцатеричную систему счисления;

endl — при выводе вставляет в поток символ перевода строки и выгружает содержимое буфера;

ends — при выводе в строковый поток вставляет символ конца строки;

flush — при выводе выгружает содержимое буфера.

Библиотека содержит и другие манипуляторы, в том числе с параметрами, но для использования по-
следних необходимо подключить файл <iomanip>:

setw(ширина_поля_вывода) — устанавливает ширину поля для следующего элемента вывода;

setfill(символ_заполнения) — устанавливает символ заполнения полей.

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

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 49

Ввод-вывод пользовательских типов


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

ostream &operator <<(ostream &поток, const класс &объект)

// обычно операторы вида: поток << ...;

return поток;

istream &operator >>(istream &поток, класс &объект)

// обычно операторы вида: поток >> ...;

return поток;

Основы работы с файловыми потоками


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

void open(const char *, int, int);

Для большинства типичных случаев параметры в этих методах имеют подходящие значения по умол-
чанию. При необходимости изменения значений параметров используются именованные константы-
флаги, определенные в классе ios, например, такие как ios::in. ios::out. ios::trunc и дру-
гие. Для комбинирования нескольких значений константы объединяются побитовым оператором «или»
(|).

Проверка успешности выполнения предыдущей операции с потоком выполняется методами потокового


класса:

bool operator !();

bool good();

bool bad();

bool fail();

Центр «Специалист» www.specialist.ru


50 Программирование на Visual C++

Закрытие файлового потока выполняет метод

void close();

Пример – класс String (окончание)


//Listing 4.4. Файл String.h – определение класса String
// ...

class String
{
public:
// ...

friend std::ostream &operator <<(std::ostream &,const String &);


private:
// ...
};
// ...

std::istream &operator >>(std::istream &, String &);


// ...

//Listing 4.5. Файл String.cpp – реализация класса String


// ...

ostream &operator <<(ostream &os, const String &rh)


{
return os << rh.s_;
}

istream &operator >>(istream &is, String &rh)


{
char buf[256];
is.getline(buf, sizeof buf);
rh = buf;
return is;
}

//Listing 4.6. Файл useString.cpp – клиент класса String


//...
#include <fstream>
//...

int main()
{
//...
cout << "Enter your name: ";
cin >> a;
cout << "Hello, " << a << endl;

ofstream out;
out.open("test.txt");
Центр «Специалист» www.specialist.ru
Программирование на Visual C++ 51

if(out.good())
{
out << a << endl;
out.close();
ifstream in;
in.open("test.txt");
if(in.good())
{
in >> a;
cout << "String from file:" << endl;
cout << a << endl;
in.close();
}
}
}

Задачи
4.1. В классе Point измените интерфейс методов Print и Read:

std::ostream &Print(std::ostream & = std::cout);

bool Read(std::istream & = std::cin);

и реализуйте c их помощью перегрузку операторов << и >>.

4.2. Реализуйте требования задачи 4.1. для классов Pixel и Line (задача 3.1) и String (листинги
4.1.-4.6.).
*
4.3. Для классов Date и Time (задача 3.2 .) реализуйте перегрузку операторов сравнения и операто-
ров << и >>. Какие еще операторы Вы сочтете возможным и полезным перегрузить?
* **
4.4 . Перепишите код класса Organizer (задача 3.3 ) при помощи перегруженных операторов классов
Date, Time и String (задачи 4.2.-4.3. и листинги 4.1.-4.6.). Стал ли код этого класса и код его ис-
пользование проще и понятнее?

4.5. Удастся ли Вам использовать материал этого модуля для дальнейшей «проработки» класса Traf-
ficLights (задача 3.5)?

Вопросы для обсуждения и «углубления»


4.1. Для класса Point рассмотрите следующую интерпретацию. Пусть точка является концом вектора,
выходящего из начала координат (кстати, может быть, класс «точка» в этом случае стоит переимено-
вать в класс «вектор»?). Если перегрузить операторы + и - как сложение и вычитание координат, а
также оператор умножения как скалярное умножение векторов и умножение вектора на скаляр, то мож-
но получить реализацию алгебры векторов.
Следует ли в этом случае сохранять требование не отрицательности значений координат?

4.2. Выскажите Ваше мнение о том, какие еще полезные с Вашей точки зрения операторы можно было
бы перегрузить для классов Pixel и Line (задачи 3.1 и 4.2) и стоит ли это делать?

Центр «Специалист» www.specialist.ru


52 Программирование на Visual C++

4.3. Является ли перегрузка операторов необходимым атрибутом объектно-ориентированного подхода?


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

4.4. Выскажите Ваше мнение о том, почему в классе String оператор + и операторы сравнения реа-
лизованы как свободные функции, а не как члены класса? Какие рекомендации на этот счет Вы знаете
или могли бы предложить? Сравните эти решения с различных точек зрения: универсальности, эффек-
тивности, «лаконичности» и т.д. А какое бы решение Вы предложили в качестве максимально эффек-
тивной реализации?

4.5. В классе String существует конструктор преобразования из строки символов языка C (const
char *) в тип String. Почему не рекомендуется в подобном случае иметь в классе одновременно и
реализацию обратного преобразования? Рассмотрите все положительные и отрицательные последст-
вия реализации в классе оператора
operator const char *() { return s_; }
Что бы Вы предложили сделать в ситуации, когда в классе необходимы оба преобразования?

4.6. Рассмотрите реализацию в классе String оператора преобразования в тип bool


operator bool() { return n_ != 0; }
И примеры его использования:
cout << "String b is" << ( b ? "n‟t" : "") << " empty" << endl;
cout << "String e is" << (!e ? "" : "n‟t") << " empty" << endl;
Изучите все «побочные эффекты» от наличия в классе этого оператора.

4.7*. Рассмотрите возможность реализации в классе String перегрузки операторов += и + для типа
char. Насколько эффективной она будет по Вашему мнению? Предложите модифицированную реали-
зацию класса String, которая бы решила эту (и ряд других) проблем.
*
4.8 . Класс, в котором реализован перегруженный оператор () – вызов функции, часто называют клас-
сом-«функтором», так как он позволяет создавать объекты-«функторы», которые «ведут себя как функ-
ции». Такие объекты используются, например, в ряде «инструментов» STL – стандартной библиотеки
шаблонов языка C++.

4.9**. Класс-«функтор» – не единственное применение перегруженного оператора ().Он используется


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

4.10***. Перегрузка оператора -> приводит к концепции создания «интеллектуальных» (smart) указате-
лей. Ряд smart-указателей являются частью стандартной библиотеки языка C++.

4.11***. Перегрузка операторов new и delete предоставляет возможность создавать для класса соб-
ственные «аллокаторы» – механизмы распределения памяти. В каких случаях это может понадобить-
ся? Какие проблемы возникают в этой задаче?

4.12**. Как и для каких целей выполняется программистом создание собственных манипуляторов для
классов стандартных потоков ввода/вывода библиотеки STL?.
Центр «Специалист» www.specialist.ru
Программирование на Visual C++ 53

Модуль 5. Статические элементы данных

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


Класс может содержать в своем составе статические компоненты – как данные (поля), так и функции
(методы).

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


для всего класса (их называют иногда «свойства класса») и требуют отдельных инструкций для выде-
ления им памяти и инициализации.

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

Для доступа к закрытым (private) и защищенным (protected) статическим данным могут использо-
ваться как обычные, так и статические открытые функции-компоненты.

Определение и использование статического поля


Объявление статического поля в классе:

static тип идентификатор;

Инициализация статического поля выполняется вне класса (на уровне файла):

тип класс::идентификатор = выражение;

Доступ к открытому статическому полю через имя класса:

класс::идентификатор

Доступ к открытому статическому полю через объект:

объект.идентификатор
объект.класс::идентификатор

Примечание. Для статических полей действует та же рекомендация, что и для нестатических – не сле-
дует предоставлять к данным класса открытый доступ.

Объявление и вызов статического метода


Объявление статического метода в классе:

static тип идентификатор(формальные_аргументы);

Вызов открытого статического метода класса через имя класса:

класс::идентификатор(фактические_параметры)
Центр «Специалист» www.specialist.ru
54 Программирование на Visual C++

Вызов открытого статического метода класса через объект:

объект.идентификатор(фактические_параметры)
объект.класс::идентификатор(фактические_параметры)

Константные объекты и методы


Класс может содержать в своем составе константные компоненты – как данные (поля), так и функции
(методы).

Объявление константного поля в классе:

const тип идентификатор;

Инициализация константного поля должна выполняться в каждом конструкторе и только посредством


списка инициализации, после чего никакие модификации его значения не допускаются.

Статические константные поля могут инициализироваться в определении класса:

static const тип идентификатор = выражение;

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

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

Объявление константной функции в классе:

тип идентификатор(формальные_аргументы) const;

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

тип идентификатор(формальные_аргументы) const


{ тело_функции }

тип класс::идентификатор(формальные_аргументы) const


{ тело_функции }

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

Поле класса, отмеченное квалификатором mutable, может быть модифицировано константным мето-
дом.

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 55

Пример – класс Pixel


Ниже приводится одно из возможных решений задач 2.2, 3.1, 4.2. Особо отметим, что в данном приме-
ре демонстрируется способ создания нового класса, именуемый композицией, или «внедрением»
объекта существующего класса в объект создаваемого класса (другой способ – наследование – явля-
ется темой модуля 6).

Дополнительно к требованиям перечисленных задач, в классе Pixel иллюстрируется материал теку-


щего модуля – константные и статические компоненты класса. Для работоспособности этого примера
необходимо в классе Point явно описать константные методы. Особенности использования
константного поля посвящена задача 5.1.Применение статических компонентов демонстрируется на
традиционном примере – подсчете существующих объектов. (И уберите в Point отладочную
печать).
//Listing 5.1. Файл Pixel.h
#ifndef PIXEL_H
#define PIXEL_H
#include "Point.h"
class Pixel
{
public:
enum Color {black,blue,green,red=4,white=15};

Pixel(const Point &, const Color &c = black);


Pixel(int x =0, int y =0, const Color &c = black);
Pixel(const Pixel &);
~Pixel();

int GetX() const { return p_.GetX(); }


int GetY() const { return p_.GetY(); }
void SetX(int x);
void SetY(int y);
void Move(int delta_x, int delta_y);
double Dist(const Pixel &) const;

const Color &GetColor() const;


void SetColor(const Color &);

std::ostream &Print(std::ostream &os = std::cout) const;


bool Read(std::istream &is = std::cin);
static int GetCounter() { return cnt_; }
private:
Point p_;
const Color c_;

static int cnt_;


};
std::ostream &operator <<(std::ostream &, const Pixel &);
std::istream &operator >>(std::istream &, Pixel &);

#endif

Центр «Специалист» www.specialist.ru


56 Программирование на Visual C++

//Listing 5.2. Файл Pixel.cpp

#include <iostream>
#include "Pixel.h"
using namespace std;
int Pixel::cnt_ = 0;

Pixel::Pixel(const Point &p, const Color &c)


:p_(p), c_(c)
{
++cnt_;
}

Pixel::Pixel(int x, int y, const Color &c)


:p_(x,y), c_(c)
{
++cnt_;
}

Pixel::Pixel(const Pixel &px)


:p_(px.p_), c_(px.c_)
{
++cnt_;
}

Pixel::~Pixel()
{
--cnt_;
}
void Pixel::SetX(int x)
{
p_.SetX(x);
}
void Pixel::SetY(int y)
{
p_.SetY(y);
}

void Pixel::Move(int delta_x, int delta_y)


{
p_.Move(delta_x,delta_y);
}

double Pixel::Dist(const Pixel &px) const


{
return p_.Dist(px.p_);
}

ostream &Pixel::Print(ostream &os) const


{
return p_.Print(os) << c_;
}

bool Pixel::Read(istream &is)


{
if(p_.Read(is) && is >> (int&)c_ ) return true;

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 57

if(!is) {is.clear(); while(is.get() != '\n');}


return false;
}
const Pixel::Color &Pixel::GetColor() const
{
return c_;
}
void Pixel::SetColor(const Color &c)
{
c_ = c;
}

ostream &operator <<(ostream &os, const Pixel &px)


{
return px.Print(os);
}

istream &operator >>(istream &is, Pixel &px)


{
px.Read(is);
return is;
}

//Listing 5.3. Файл usePixel.cpp

#include <iostream>
#include "Pixel.h"
using namespace std;
void test(const Pixel &px)
{
px.Print(cout << "In test(const Pixel &px): ") << endl;
}
int main()
{
const Point origin(0,0);
// origin.Move(1,1);
origin.Print(cout << "Point origin: ") << endl;
cout << Pixel::GetCounter() << endl;
{
Pixel a(10,20,Pixel::blue), b(Point(20,30),Pixel::red);

cout << a.GetCounter() << endl;


cout << Pixel::GetCounter() << endl;
Pixel c = a;
c = a;
test(a);
cout << "Pixel a: " << a << endl;
cout << "Pixel b: " << b << endl;

b.Move(20,30);
b.SetColor(Pixel::green);

Центр «Специалист» www.specialist.ru


58 Программирование на Visual C++

cout << "Pixel b changed: " << b << endl;


cout << "Distance from "<< a << " to " << b
<< " = " << a.Dist(b) << endl;

cout << Pixel::GetCounter() << endl;


}
cout << Pixel::GetCounter() << endl;
}

Задачи
5.1. В классе Pixel используйте квалификатор const при описании поля «цвет», реализовав тем са-
мым правило – «пиксель не может изменить свой цвет, полученный им при рождении». Попробуйте
вначале предсказать и объяснить, какие строки в листинге 5.1 – 5.3 перестанут компилироваться и по-
чему, а затем сравните свой ответ с сообщениями компилятора. Восстановите работоспособность кода,
закомментировав проблемные строки.

5.2. Вернитесь к ранее разработанным классам Line, Date, Time, String и модифицируйте их
код с учетом материала текущего модуля. Выскажите Ваше мнение, к каким последствиям для кода,
использующего эти классы, приведут внесенные изменения?

5.3. В классе String реализуйте перегруженный оператор индексирования с квалификатором


const.Объясните, в каких ситуациях будет вызываться эта версия оператора.

5.4*. Перепишите код класса Organizer (задача 4.4**) при помощи модифицированных классов Date,
Time и String (задача 5.2). Стал ли код использования этого класса более надежным? А более эф-
фективным?

5.5. Удастся ли Вам использовать материал этого модуля для класса TrafficLights (задача 4.5)?

Вопросы для обсуждения и «углубления»


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

5.2. В классе Pixel применение статических компонентов иллюстрировалось на примере создания


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

5.3*. Еще одним применением является создание в классе статического метода Create. Какую «мо-
дель» программирования призвано поддерживать это решение?

5.4. Объясните, почему статические методы не имеют доступа к нестатическим компонентам-данным?


Почему нестатические методы имеют доступ и к статическим, и к нестатическим компонентам-данным?

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 59

Модуль 6. Наследование и полиморфизм

Понятие наследования в ООП и его реализация в C++


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

Производные классы часто называют также классами-потомками, дочерними классами или подкласса-
ми, а базовые – классами-предками, родительскими классами или суперклассами. В свою очередь,
производные классы могут выступать в качестве базовых для порождения следующего «поколения»
классов и так далее.

Объединенные отношением наследования классы образуют иерархию наследования, которая графи-


чески изображается в виде направленного графа. Узлы графа – это классы, а отношение наследования
выражается ребрами-стрелками, направленными от потомков к предкам.

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

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

В контексте наследования начинает играть самостоятельную роль спецификатор доступа protected


— защищенные компоненты, которые недоступны для внешнего кода, но доступны для кода классов-
потомков.

Кроме того, между классами, связанными отношением открытого (public) наследования, существуют
неявные «восходящие» приведения: указатели и ссылки на производный класс неявно приводятся,
соответственно, к указателям и ссылкам на базовый класс.

Большая часть теории ООП посвящена вопросам одиночного открытого наследования, значительно
меньшую ее часть занимает одиночное закрытое (private) наследование.

Определение производного класса


class идентификатор : список_базовых_классов

определение_компонентов
};

где список_базовых_классов содержит их имена, а также (необязательные) спецификаторы доступа к


компонентам каждого класса.

Центр «Специалист» www.specialist.ru


60 Программирование на Visual C++

Пример – класс Pixel


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

//Listing 6.1. Файл Pixel.h

#ifndef PIXEL_H
#define PIXEL_H
#include "Point.h"

class Pixel : public Point


{
public:
enum Color {black,blue,green,red=4,white=15};
Pixel(const Point &, const Color &с = black);
Pixel(int x = 0, int y = 0, const Color &с = black);
~Pixel();

const Color & GetColor() const;


void SetColor(const Color &);
std::ostream &Print(std::ostream &os = std::cout) const;
bool Read(std::istream &is = std::cin);

private:
Color c_;
};
#endif

//Listing 6.2. Файл Pixel.cpp

#include <iostream>
#include "Pixel.h"
using namespace std;

Pixel::Pixel(const Point &p, const Color &c)


:Point(p), c_(c)
{}
Pixel::Pixel(int x, int y, const Color &c)
:Point(x,y), c_(c)
{}

Pixel::~Pixel()
{}

const Pixel::Color &Pixel::GetColor() const


{
return c_;
}

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 61

void Pixel::SetColor(const Color &c)


{
c_ = c;
}
ostream &Pixel::Print(ostream &os) const
{
return Point::Print(os) << c_;
}
bool Pixel::Read(istream &is)
{
if( Point::Read(is) && is >> (int&)c_ ) return true;

if(!is) { is.clear(); while(is.get() != '\n');}


return false;
}

//Listing 6.3. Файл usePixel.cpp

#include <iostream>
#include "Pixel.h"
using namespace std;
void test(const Point &r)
{
r.Print(cout << "test(const Point &) ") << endl;
}
void test(const Point *p)
{
p->Print(cout << "test(const Point *) ") << endl;
}
int main()
{
Pixel a;
a.Print(cout << "Pixel a: ") << endl;
a.Move(10,20);
a.Print(cout << "Pixel a changed: ") << endl;
Pixel b(30,50,Pixel::green);
b.Print(cout << "Pixel b: ") << endl;
b.Move(10,10);
b.SetColor(Pixel::red);
b.Print(cout << "Pixel b changed: ") << endl;
b.Point::Print(cout << "Pixel b as Point: ") << endl;
cout << "Dist = " << a.Dist(b) << endl;

Pixel *pd = &a;


Point *pb = &a; //"восходящее" приведение
pb = pd; //"восходящее" приведение
// pd = pb; //не компилируется
Pixel &rd = b;
Point &rb = b; //"восходящее" приведение

Point pt(77,77);
Центр «Специалист» www.specialist.ru
62 Программирование на Visual C++

test(pt); test(&pt);
test(b); test(&b);
}

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

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

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

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

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


полиморфный кластер.

Пример – классы Point и Pixel


Для того чтобы увидеть полиморфизм «в действии», рассмотрите классы Point и Pixel, связанные
отношением наследования, и объявите в них виртуальной функцию Print:

virtual std::ostream &Print(std::ostream &os = std::cout) const;

Рассмотрите, как теперь изменился результат работы кода функций test (см. листинг 6.3.).

Чистые виртуальные функции и абстрактные базовые классы


Виртуальная функция-член (базового) класса, объявленная, но не имеющая в своем классе реализа-
ции, называется чистой виртуальной функцией, а класс, содержащий хотя бы одну чистую виртуаль-
ную функцию, — абстрактным базовым классом. Не разрешается создание объектов абстрактного
базового класса – его назначение – предоставить интерфейс к классам-потомкам, которые и должны
обеспечить реализацию унаследованным чистым виртуальным функциям, тем самым став конкрет-
ными классами, чьи объекты создавать разрешено.
Центр «Специалист» www.specialist.ru
Программирование на Visual C++ 63

Пример – класс Shape


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

Для того чтобы смоделировать работу с классом Shape «в действии», объявите классы Point и
Pixel производными от класса Shape.В самом классе Shape закомментируйте объявления графиче-
ских (наиболее интересных и пока не реализованных, но позже мы вернемся и к ним) функций.

//Listing 6.4. Файл Shape.h


#ifndef SHAPE_H
#define SHAPE_H

class View;

class Shape //ABC - Abstract Base Class (interface)


{
public:
virtual ~Shape() {}
//чистые виртуальные функции
virtual std::ostream &Print(std::ostream &os = std::cout) const =0;
virtual void Move(int delta_x, int delta_y) =0;

//другие чистые виртуальные функции


// virtual void Draw(View &) const =0;
// virtual void Erase(View &) const =0;
// virtual void Rotate(double angle) =0;
// virtual void Expand(double factor) =0;
};

#endif

//Listing 6.5. Файл useShape.cpp


#include <iostream>
#include "Shape.h"
#include "Point.h"
#include "Pixel.h"
using namespace std;
void PrintAll(Shape **p, int n)
{
while(n-->0) (*p++)->Print() << endl;
}
void ScrollUp(Shape **p, int n, int h)
{
while(n-->0) (*p++)->Move(0,h);
}

Центр «Специалист» www.specialist.ru


64 Программирование на Visual C++

int main()
{
Shape *a[] = { new Point(10,20), new Pixel(30,40,Pixel::red) };
const int n = sizeof a / sizeof *a;

PrintAll(a,n);
ScrollUp(a,n,100);
PrintAll(a,n);
for(int i=0; i<n; ++i) delete a[i];
}

Множественное наследование
Язык C++, наряду с небольшим количеством других объектных языков, допускает множественное на-
следование — порождение класса-потомка от двух и более базовых классов. При открытом множест-
венном наследовании класс-потомок наследует свойства и поведение всех своих классов-предков.

Пример – класс Marker


//Listing 6.6. Файл Marker.h
#ifndef MARKER_H
#define MARKER_H
#include "Point.h"
#include "String.h"
class Marker :
public Point,
public String
{
public:
std::ostream &Print(std::ostream & =std::cout) const;
Marker(int =0, int =0, const char * ="");
Marker(const Point &, const String &);
~Marker();
};
#endif

//Listing 6.7. Файл Marker.cpp


#include <iostream>
#include "Marker.h"
using namespace std;

Marker::Marker(int x, int y, const char *str)


:Point(x,y), String(str)
{}
Marker::Marker(const Point &p, const String &s)
:Point(p), String(s)
{}
ostream &Marker::Print(ostream &os) const
{
return String::Print(Point::Print(os)<<",\"")<<"\"";
}
Центр «Специалист» www.specialist.ru
Программирование на Visual C++ 65

//Listing 6.8. Файл useMarker.cpp

#include <iostream>
#include "Marker.h"
using namespace std;

int main()
{
Marker a(10,20,"Marker a");
a.Print() << endl;
a.Move(100,200);
a += " moved";
a.Print() << endl;
}

Виртуальные базовые классы


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

Пример – класс WorkingStudent


//Listing 6.9. Файл Person.h
#ifndef PERSON_H
#define PERSON_H
class Person
{
const char *name_;
public:
virtual std::ostream &Print(std::ostream & =std::cout) const;
Person(const char * ="Unknown");
virtual ~Person();
};

#endif

//Listing 6.10. Файл Person.cpp

#include <iostream>
#include "Person.h"
using namespace std;

Person::Person(const char *name)


:name_(name)
{}
Person::~Person()
{}

Центр «Специалист» www.specialist.ru


66 Программирование на Visual C++

ostream &Person::Print(ostream &os) const


{
return os << name_;
}

//Listing 6.11. Файл Student.h


#ifndef STUDENT_H
#define STUDENT_H

#include "Person.h"

class Student : virtual public Person


{
int year_;
public:
virtual std::ostream &Print(std::ostream & =std::cout) const;
Student(const char *, int);
virtual ~Student();
};
#endif

//Listing 6.12. Файл Student.cpp


#include <iostream>
#include "Student.h"
using namespace std;
Student::Student(const char *name, int year)
:Person(name), year_(year)
{}

Student::~Student()
{}
ostream &Student::Print(ostream &os) const
{
return Person::Print(os)<<" - student of "<< year_<<" year";
}

//Listing 6.13. Файл Worker.h


#ifndef WORKER_H
#define WORKER_H
#include "Person.h"
class Worker : virtual public Person
{
const char *department_;
public:
virtual std::ostream &Print(std::ostream & =std::cout) const;
Worker(const char *, const char *);
virtual ~Worker();
};
#endif

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 67

//Listing 6.14. Файл Worker.cpp

#include <iostream>
#include "Worker.h"
using namespace std;

Worker::Worker(const char *name, const char *dept)


:Person(name), department_(dept)
{}
Worker::~Worker()
{}

ostream &Worker::Print(ostream &os) const


{
return Person::Print(os) << " - worker of "
<< department_ << " department";
}

//Listing 6.15. Файл WorkingStudent.h

#ifndef WORKINGSTUDENT_H
#define WORKINGSTUDENT_H
#include "Student.h"
#include "Worker.h"

class WorkingStudent :
public Student,
public Worker
{
public:
virtual std::ostream &Print(std::ostream & =std::cout) const;
WorkingStudent(const char *, int, const char *);
virtual ~WorkingStudent();
};
#endif

//Listing 6.16. Файл WorkingStudent.cpp

#include <iostream>
#include "WorkingStudent.h"
using namespace std;
WorkingStudent::WorkingStudent(const char *name, int year, const char *dept)
:Person(name), Student(name,year), Worker(name,dept)
{}

WorkingStudent::~WorkingStudent()
{}
ostream &WorkingStudent::Print(ostream &os) const
{
return Worker::Print(Student::Print(os) << " and ");
}

Центр «Специалист» www.specialist.ru


68 Программирование на Visual C++

//Listing 6.17. Файл VirtBaseClass.cpp

#include <iostream>
#include "Person.h"
#include "Student.h"
#include "Worker.h"
#include "WorkingStudent.h"
using namespace std;
void WhoAreYou(const Person &p)
{
p.Print() << endl;
}

int main()
{
Person a("Ivanov Ivan");
WhoAreYou(a);
Student b("Petrov Peter", 2);
WhoAreYou(b);

Worker c("Sidorov Sidor", "Biblio");


WhoAreYou(c);
WorkingStudent d("Kuzmin Kuzma", 3, "Biblio");
WhoAreYou(d);
}

Задачи
6.1. Реализуйте класс Pixel посредством не открытого, а закрытого (private) наследования. По-
пробуйте вначале предсказать и объяснить, какие строки в листинге 6.1 – 6.3 перестанут компилиро-
ваться и почему, а затем сравните свой ответ с сообщениями компилятора. Восстановите работоспо-
собность кода, либо доопределив соответствующие методы в классе Pixel, либо, при невозможности
такого решения, закомментировав проблемные строки. Сравните получившийся код с листингом 5.1. –
5.3.

6.2. Вернитесь к ранее разработанным классам Line, Triangle, Pixel и объявите их производны-
ми от класса Shape. Обеспечьте работоспособность кода листинга 6.5. для этого случая.

6.3*. В задаче 6.2. реализуйте остальные методы класса Shape (листинг 6.4.), для чего обратитесь к ус-
**
ловиям задачи 2.8 .

6.4**. Рассмотрите возможность расширения функциональности класса Organizer (задача 5.4**).


Пусть, кроме записей о ежедневных событиях, этот класс помогает «организовать» работу со списками
«памятных записей» (дни рождения и т.п.), списками телефонов или более обобщенно – со «списком
контактов».

6.5**. Используйте материал этого модуля для класса TrafficLights (задача 5.5).
*** **
6.6 . Если у светофора (задача 6.5 .) появится секция со стрелкой – сильно ли изменится Ваш код?

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 69

Вопросы для обсуждения и «углубления»


6.1. Почему «восходящее» приведение указателей и ссылок выполняется компилятором неявно, а
«нисходящее» требует явного приведения?

6.2**. Как реализуется динамическое связывание при вызове виртуальной функции?


*
6.3 . В каких ситуациях при вызове виртуальной функции «механизм виртуализации» не работает?

6.4. Почему статическая функция не может быть виртуальной?

6.5. В теории объектно-ориентированного программирования отношение открытого наследования опи-


сывают фразой «… is a …», выражая таким способом отношение подобия: «класс-потомок есть класс-
предок». Например, «Пиксель (Pixel) – это точка (Point) , но цветная».
Отношение закрытого наследования описывают фразой «… has a …», выражая отношение реализа-
ции: «класс-потомок имеет в своем составе класс-предок».
Прокомментируйте такие иллюстрации отношений между двумя классами. А как выражается отношение
защищенного наследования?
*
6.6 . В теории ООП считается, что открытое наследование построено «правильно», если отношение
между классами удовлетворяет принципу подстановки Лисков – Liskov Substitution Principle (LSP). При-
ведите и обсудите формулировку LSP.

6.7*. Часто отношение открытого наследования интерпретируют как отношение «обобщения – специа-
лизации»: класс-предок выражает более общее понятие, а класс-потомок – более частное или специ-
альное. Иначе, предок – это вид или тип, а потомок – подвид, подтип. С этой точки зрения, установите
«правильное» отношение вид/подвид для классов «прямоугольник» и «квадрат».
**
6.8 . Сформулируйте «правильные» отношения между классами: общий вид – «птица», подвиды:
«орел», «воробей», «пингвин», «страус».

6.9**. Базовый класс без членов-данных, содержащий лишь открытые чистые виртуальные функции,
часто называется интерфейсным классом, а открытое наследование ему – наследованием (только) ин-
терфейса. При открытом наследовании конкретному классу наследуется и интерфейс, и реализация.
Закрытое наследование называют наследованием (только) реализации. Обсудите эти и другие возмож-
ные в C++ промежуточные способы наследования интерфейса и реализации.

6.10*. В каких отношениях между собой находятся private, protected и public секции базового и
производного классов при различных способах наследования: private, protected и public соот-
ветственно?
*
6.11 . При множественном наследовании может происходить дублирование данных несколько раз унас-
ледованного косвенного базового класса («дальнего предка»). Всегда ли нежелательно такое дублиро-
вание? Приведите примеры, когда оно, наоборот, необходимо.
*
6.12 . Какой тип наследования в C++ называется «подмешиванием» базового класса?

Центр «Специалист» www.specialist.ru


70 Программирование на Visual C++

6.13**. Объясните смысл рекомендации экспертов: «если в классе есть хотя бы одна виртуальная функ-
ция, то деструктор класса обычно следует объявлять виртуальным».

6.14**. Объясните смысл рекомендации экспертов: «избегайте размещения в секции protected чле-
нов-данных класса». Для чего же тогда используется эта секция?

6.15**. Какой способ построения новых классов следует предпочитать: композицию или наследование?
Объясните смысл рекомендации экспертов: «всюду, где можно – используйте композицию, а наследо-
вание – только там, где необходимо».

6.16*. Прокомментируйте следующее утверждение: «при хорошо спланированных отношениях наследо-


вания между классами, дерево наследования должно, скорее, расти в ширину, чем в высоту».

6.17*. Является ли использование наследования классов в проекте необходимым, для того, чтобы счи-
тать, что он «объектно-ориентированный»? А достаточным?

6.18*. Пусть в некотором классе нет ни одной виртуальной функции. О каких намерениях разработчика
по использованию этого класса свидетельствует такой дизайн?

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 71

Модуль 7. Шаблоны функций и классов

Назначение и использование шаблонов функций


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

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

Определение шаблона функции:

template<список_параметров_шаблона>
заголовок_функции
{ тело_функции }

Каждый параметр в списке начинается словом typename (или class) и имеет уникальный идентифика-
тор.

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

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

Пример – шаблон функции max

//Listing 7.1. Шаблон функции


#include <iostream>
#include <cstring>

using std::cout;
using std::endl;

template<typename T> //заголовок шаблона, T – параметр-тип


T max(T a, T b)
{
return a > b ? a : b;
}

const char *max(const char *a, const char *b) //перегруженный шаблон
{
return std::strcmp(a,b) > 0 ? a : b;
}

Центр «Специалист» www.specialist.ru


72 Программирование на Visual C++

int main()
{
int c = 5, d = 10, im;
im = max(c, d); //генерация для T <--- int
cout << "max(" << c << "," << d << ") = " << im << endl;
double x = 1.2, y = 0.5, dm;
dm = max(x, y); //генерация для T <--- double
cout << "max(" << x << "," << y << ") = " << dm << endl;

im = max(c+d, 14); //вызывается ранее созданная


//int max(int, int);
cout << "max(" << c <<"+"<< d << "," << 14 << ") = " << im << endl;

const char *p = "overlay", *q = "overload", *pm;


pm = max(p, q); //используется перегруженный
//вариант шаблонной функции
cout << "max(" << p << "," << q << ") = " << pm << endl;
dm = max(double(c),x); //явное приведение аргументов
cout << "max(" << c << "," << x << ") = " << dm << endl;

dm = max<double>(c,x); //специализация шаблона


cout << "max(" << c << "," << x << ") = " << dm << endl;
}

Назначение и использование шаблонов классов


Шаблоны классов автоматизируют построение (макрогенерацию) классов и имеют следующий фор-
мат:

template<список_параметров_шаблона>
определение_класса

Если параметр в списке начинается словом typename (или class), то он описывает параметр-тип, ко-
торый используется для спецификации различных компонентов шаблонного класса. В списке парамет-
ров шаблона класса допускаются и целочисленные (не типовые) параметры, они рассматриваются как
константы внутри определяемого класса.

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

template<список_параметров_шаблона>
класс<список_имен_параметров_шаблона>::имя_функции (формальные_аргументы)
{ тело_функции }

Создание объекта класса по шаблону имеет синтаксис:

имя_шаблона<список_фактических_параметров> идентификатор(инициализатор);

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 73

Пример – шаблон класса Stack


//Listing 7.2. Шаблон класса "Стек" (LIFO) ограниченного размера size
#ifndef STACK_H
#define STACK_H
template<typename T, int size>
class Stack
{
T stk[size];
int top;
public:
Stack();
~Stack();
void Push(const T &);
T Pop();
bool IsEmpty();
bool IsFull();
};

template<typename T, int size>


Stack<T,size>::Stack()
{ top =0; }
template<typename T, int size>
Stack<T,size>::~Stack()
{}

template<typename T, int size>


void Stack<T,size>::Push(const T &x)
{
if(!IsFull()) stk[top++]=x;
else cerr << "\nStack is full\n";
}

template<typename T, int size>


T Stack<T,size>::Pop()
{
if(!IsEmpty()) return stk[--top];
else
{
cerr << "\nStack is empty\n";
return T();
}
}
template<typename T, int size>
bool Stack<T,size>::IsEmpty()
{
return !top;
}

template<typename T, int size>


bool Stack<T,size>::IsFull()
{
return top == size;
}

#endif

Центр «Специалист» www.specialist.ru


74 Программирование на Visual C++

// Listing 7.3. Использование класса "Стек"

#include <iostream>
using namespace std;

#include "Stack.h"
int main()
{
//Стек для int
Stack<int,10> iStack;
for(int i=0; !iStack.IsFull(); ++i)
iStack.Push(i);
iStack.Push(777); //контроль переполнения стека

while(!iStack.IsEmpty())
cout << iStack.Pop() << " ";
iStack.Pop(); //контроль исчерпания стека
//Стек для char
Stack<char,26> cStack;
for(int i=0; !cStack.IsFull(); ++i)
cStack.Push('A'+i);
while(!cStack.IsEmpty())
cout << cStack.Pop();
cout << endl;
}

Задачи
*
7.1. Разработайте шаблон класса для поддержки динамических массивов как обобщение задачи 1.4 .
Протестируйте его работу с разными классами.

7.2. Разработайте шаблон класса для реализации односвязного списка узлов как обобщение задачи
1.6*. Протестируйте его работу с разными классами.

7.3. Реализуйте для задач 7.1. и 7.2. условие задачи 1.7***.

7.4. Примените для задачи 6.2. Ваше решение задач 7.1.-7.3.

7.5*. Расширьте решение задачи 7.4. на задачу 6.3*.

7.6. Примените для класса Organizer (задача 6. 4**.) Ваше решение задач 7.1-7.3.

7.7. Используйте материал этого модуля для класса TrafficLights (задача 6.6**).

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 75

Вопросы для обсуждения и «углубления»


7.1. Прокомментируйте следующее утверждение – шаблоны представляют еще один (третий) вид по-
лиморфизма, реализованный в C++.

7.2. Какие ограничения налагает код шаблона функции из листинга 7.1. на возможные значения его па-
раметра T?

7.3. Какие ограничения налагает код шаблона класса из листинга 7.2.-7.3. на возможные значения его
параметра T?

7.4. Рассмотрите возможность обобщения шаблона функции из листинга 7.1. для работы с параметра-
ми различных типов T1 и T2. Чего больше в этом решении – достоинств или недостатков?

7.5. Рассмотрите модификации шаблона класса из листинга 7.2. для выделения памяти динамически –
для фиксированного размера контейнера и для неизвестного заранее объема. Чего больше в этом ре-
шении – достоинств или недостатков? Сможете ли здесь применить решения задач 7.1. и 7.2.?

7.6*. Шаблоны первоначально задумывались как механизм порождения типобезопасных контейнеров.


Объясните эту проблему и способ ее решения.

7.7*. Стандартная библиотека шаблонов (STL) содержит набор шаблонов различных контейнеров и на-
бор шаблонов универсальных алгоритмов их обработки, причем реализации алгоритмов имеют гаран-
тированную эффективность.

7.8*. Шаблонные классы могут вступать в отношения наследования – они могут быть и базовыми и про-
изводными как для обычных, так и для шаблонных классов. При этом параметрами шаблонных классов,
в свою очередь, могут быть и обычные и шаблонные классы. Примеры подобного построения можно
найти в библиотеке ATL (Active Template Library).

7.9**. Шаблоны поддерживают в C++ парадигму обобщенного или порождающего программирования.


Что Вы об этом можете сказать?

Центр «Специалист» www.specialist.ru


76 Программирование на Visual C++

Модуль 8. Управление исключениями

Понятие исключения. Обработка исключительных ситуаций


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

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

Блок контроля

try { список_инструкций }

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


Если инструкция вызывает некоторую функцию, то считается что все тело функции находится в try-
блоке и так далее.

Инструкция возбуждения исключения

throw выражение;

формирует (временный) объект-исключение, тип которого определяется типом выражения в инструк-


ции, прерывает нормальный ход выполнения программы в try-блоке и вызывает исполнение процеду-
ры «раскрутки» стека (stack unwinding).

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

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

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

Специализированный обработчик исключения «настроен» на перехват и обработку исключений оп-


ределенного типа и может иметь одну из форм:

catch(тип_исключения &идентификатор) { список_инструкций }

catch(тип_исключения) { список_инструкций }

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 77

Универсальный обработчик исключения перехватывает исключение любого типа

catch(...) { список_инструкций }

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

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

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

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

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

try-блоки могут быть вложенными друг в друга.

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

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

void f( ) throw(std::bad_alloc, MyException);

void f( ) throw(std::bad_alloc, MyException)

{ тело_функции }

Для поддержки возможностей обработки исключений в программе необходимо включить опцию компи-
лятора /EHsc.

Пример – класс String (с обработкой исключений)


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

Ниже рассмотрен вариант реализации перегруженного оператора индексирования в классе String, воз-
буждающего исключение стандартного типа при выходе индекса за границы строки.
Центр «Специалист» www.specialist.ru
78 Программирование на Visual C++

Другие исключительные ситуации оставлены в качестве упражнений и вопросов для углубления.

//Listing 8.1. Файл String.h (см. Listing 4.1.)


// ...
class String
{
public:
// ...
char &operator [](int i);

private:
// ...
};
// ...

//Listing 8.2. Файл String.cpp (компилировать с опцией /EHsc)


//(см. Listing 4.2.)
// ...
#include <stdexcept>
// ...

char &String::operator [](int i)


{
if(i >= 0 && static_cast<size_t>(i) < n_) return s_[i];
throw out_of_range("Index out of range");
}

//Listing 8.3. Файл useString.cpp (компилировать с опцией /EHsc)


//(см. Listing 4.3.)
#include <stdexcept>
// ...
int main()
{
//...
String e;
DUMP(e);

try
{
a[1] = a[0];

e[1] = ' # ';


}
catch(exception &ex)
{
cout << ex.what() << endl;
}

DUMP(a);
DUMP(e);
//...
}
Центр «Специалист» www.specialist.ru
Программирование на Visual C++ 79

Динамическая идентификация типов (RTTI)


Динамическая идентификация типов во время выполнения программы (RTTI – Run Time Type Informa-
tion) позволяет установить фактический производный тип объекта, который адресуется указателем или
ссылкой на базовый полиморфный класс. Фактический производный тип объекта часто называют его
динамическим типом, а тип базового класса – статическим типом.

Для выяснения динамического типа служит унарный оператор typeid(), операнд которого в этом слу-
чае должен иметь тип класса хотя бы с одной виртуальной функцией.

Выражение с оператором typeid() имеет одну из двух форм:

typeid(имя_типа)

typeid(выражение)

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

const char *type_info::name() const;

возвращающая имя класса (тип), к которому принадлежит объект, и перегружены операторы operator
== и operator !=.

RTTI-оператор typeid() — это событие времени выполнения для классов с виртуальными функция-
ми и событие времени компиляции для остальных типов. Для его работы необходимо включить опцию
/GR+ поддержки компилятором RTTI.

Оператор приведения типов dynamic_cast<>()


Как уже упоминалось в модуле 1, среди четырех новых операторов явного приведения типов выделен
оператор dynamic_cast<>(), специально предназначенный для приведений полиморфных указате-
лей и ссылок.

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

В случае «законности» приведения оператор dynamic_cast<>() возвращает преобразованные к же-


лаемому типу соответственно указатель или ссылку. При невозможности приведения возвращается ну-
левой указатель при попытке приведения указателя и возбуждается исключение типа std::bad_cast
– при попытке приведения ссылки.

Так же как и выше, для его работы необходимо включить опцию /GR+ поддержки компилятором RTTI.

Центр «Специалист» www.specialist.ru


80 Программирование на Visual C++

Пример – Полиморфные типы и операторы typeid() и dynamic_cast<>()


//Listing 8.4. Полиморфные типы, typeid() и dynamic_cast<>()
//(компилировать с опцией /GR+)

#include <iostream>
#include <typeinfo>
using namespace std;
class Point
{
public:
virtual void Print() const
{
cout << "I am Point\n";
}
virtual ~Point(){}
};

class Circle : public Point


{
public:
void Print() const
{
cout << "I am Circle\n";
}

virtual void Square() const


{
cout << "Circle has Square\n";
}
~Circle(){}
};
class Cylinder : public Circle
{
public:
void Print() const
{
cout << "I am Cylinder\n";
}

void Square() const


{
cout << "Cylinder has Square\n";
}
virtual void Volume() const
{
cout << "Cylinder has Volume\n";
}
~Cylinder(){}
};

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 81

void test(const Point *p)


{
cout << "\n--- virtual ---\n";
p->Print();

cout << "\n--- typeid() ---\n";


const type_info &ti = typeid(*p);
cout << ti.name() << endl;
if( ti == typeid(Circle) )
{
const Circle *q = static_cast<const Circle *>(p);
q->Square();
}

if( ti == typeid(Cylinder) )
{
const Cylinder *q = static_cast<const Cylinder *>(p);
q->Square();
q->Volume();
}

cout <<"\n--- dynamic_cast<>() ---\n";


if(const Circle *q=dynamic_cast<const Circle *>(p)) q->Square();
if(const Cylinder *q=dynamic_cast<const Cylinder*>(p)) q->Volume();
}

int main()
{
Point a; a.Print(); cout<<endl;
Circle b; b.Print(); b.Square(); cout<<endl;
Cylinder c; c.Print(); c.Square(); c.Volume(); cout<<endl;
cout <<"----------------------------------------------\n";
test(&a); cout<<endl;
test(&b); cout<<endl;
test(&c); cout<<endl;
}

Задачи
8.1. Для класса String перепишите фрагмент листинга 4.6. работы с файлами, используя возбуждение
исключения при невозможности создания или открытия файла. Создайте собственный класс MyFi-
leError, представляющий эти типы исключений и являющийся производным от стандартного класса
std::runtime_error. Протестируйте работу этого фрагмента. Выскажите Ваше мнение, проще ли и
понятнее ли получился код?

8.2. В реализации класса Stack (листинг 7.2.-7.3.) используйте возможности механизма обработки ис-
ключений. Какой класс (или классы) Вы используете для представления этих типов исключений? Про-
тестируйте работу кода и выскажите Ваше мнение, проще ли и понятнее ли получился код?
Центр «Специалист» www.specialist.ru
82 Программирование на Visual C++

8.3. Реализуйте требования задачи 8,2, для задач 7.1.-7.2. и 7.4.

8. 4*. Используйте материал этого модуля для класса Organizer (задача 7.6).

8.5*. Используйте материал этого модуля для класса TrafficLights (задача 7.7).

8.6*. В примере из листинга 8.4. реализуйте перегруженную функцию

void test(const Point &r) { /* ... */ },

то есть рассмотрите применение операторов typeid() и dynamic_cast<>() для ссылок. Протес-


тируйте работу этой функции.

Вопросы для обсуждения и «углубления»


8.1. Объясните различие между ошибкой в процессе исполнения программы (runtime error) и исключи-
тельной ситуацией (exception). Приведите примеры.

8.2*. В языке C нет механизма обработки исключений. Как в нем решаются и решаются ли те задачи,
которые в C++ призвана решать обработка исключений?

8.3. Поясните смысл утверждения: «обработка исключений позволяет отделить код нормального хода
исполнения программы от кода обработки аномальных состояний программы». Проиллюстрируйте это,
например, в решении задачи 8.1.

8.4. Поясните, в каких случаях обработчик исключений может определяться в форме


catch(тип_исключения) { список_инструкций }

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

8.6*. В классе String рассмотрите код конструкторов и оператора присваивания с точки зрения возмож-
ности и необходимости обработки исключений при отказе в выделении динамической памяти. Затем
еще раз вернитесь к обсуждению вопроса 3.22.

8.7**. Программный код может предоставлять различные гарантии устойчивости к исключениям: базо-
вую гарантию, строгую гарантию и гарантию отсутствия исключений. Можете ли Вы привести соответ-
ствующие определения и пояснения?

8.8**. Какие гарантии устойчивости дают теперь Ваши классы String и Stack после того, как Вы реализо-
вали в них обработку исключений?

8.9*. Прокомментируйте следующее утверждение: «для повышения надежности работы написанной


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

8.10*. Какие цели достигаются вложением try-блоков друг в друга?


**
8.11 . Прокомментируйте следующее утверждение: «для функции спецификация типов исключений,
которые могут возбуждаться при выполнении ее кода, приносит меньше пользы, чем это предполага-
Центр «Специалист» www.specialist.ru
Программирование на Visual C++ 83

лось при введении в язык C++ этой возможности, поэтому конструкцию try(список_типов_исключений)
разумнее писать как комментарий в конце строки заголовка функции».

8.12**. Используя следующий класс:

class Test
{
private:
const char *p_;
public:
Test(const char *p) : p_(p)
{
cout << "Object " << p_ << " created\n";
}
~Test()
{
cout << "Object " << p_ << " destroyed\n";
}
};

изучите работу механизма «раскрутки стека». Для этого напишите программу, в которой объекты этого
класса создаются до и после try-блока, в try-блоке до и после вызова некоторой функции, в самой этой
функции до и после вложенного вызова еще одной функции, в которой объекты создаются до и после
точки возбуждения исключения. Сравните количество и порядок созданных и уничтоженных объектов
при нормальном ходе выполнения и при возбуждении исключения. Сформулируйте результат Ваших
исследований.
** **
8.13 . В задаче 8.12 . в тело первой функции вложите еще один try-блок, охватывающий вызов второй
функции, и изучите работу обработчиков исключений для нескольких типов исключений, один из кото-
рых перехватывается внутренним блоком, второй – внешним, третий – обоими блоками. Реализуйте
«доставку» в обработчики информации о числовом коде и текстовом сообщении возбуждаемого исклю-
чения.

8.14**. Что произойдет с объектами класса Test в задачах 8.12**. и 8.13**., если некоторые из них будут
создаваться не на стеке, а в динамической памяти? Какое решение для такого случая Вы можете пред-
***
ложить? (Может быть, Вам поможет возврат к обсуждению вопроса 4.10 .)
*
8.15 . Почему в программе рекомендуется избегать явного использования информации о типе (RTTI)?

8.16. В чем сходство и в чем различие задач, которые решаются операторами typeid() и dynam-
ic_cast<>()?

8.17. Почему для использования typeid(), dynamic_cast<>() и обработки исключений требуется


дополнительно «включать» соответствующие опции компилятора?

Центр «Специалист» www.specialist.ru


84 Программирование на Visual C++

Модуль 9. Использование Microsoft Visual Studio

Интерфейс Visual Studio


Интегрированная среда разработки Visual Studio предоставляет программисту широкий набор возмож-
ностей для разработки, отладки, тестирования и развертывания различных типов приложений:

- создание, открытие, закрытие, сохранение проектов и решений, web-узлов и отдельных файлов – ме-
ню Файл (File);

- набор команд редактирования – меню Правка (Edit);

- управление различными окнами и панелями инструментов – меню Вид (View);

- управление содержимым текущего проекта – меню Проект (Project);

- набор команд построения проектов и решений – меню Построение (Build);

- набор команд для отладки – меню Отладка (Debug);

- и другие возможности – меню Сервис (Options), Тест (Test), Окно (Window), Справка (Help).

Создание консольного приложения и приложения Win32


Visual Studio содержит большой набор шаблонов для построения заготовок различных типов приложе-
ний при помощи мастеров (Wizards). Мы на занятиях используем лишь два типа:

- консольное приложение Win32 (Win32 Console Application) – приложение с простейшим интерфей-


сом, имитирующим работу «под MS DOS»;

- проект Win32 (Win32 Application) – приложение с графическим пользовательским интерфейсом (GUI


- Graphics User Interface).

Для создания приложения следует выполнить в меню команду Файл/Создать/Проект…


(File/New/Project…) и выбрать нужный шаблон.

Понятие проекта и просмотр компонентов проекта


Понятие проекта в Visual Studio многозначно:

- во-первых, это совокупность всех файлов – исходных, промежуточных, вспомогательных и оконча-


тельных, которые создаются и используются при построении приложения;

- во-вторых, это совокупность папок (директорий, folders), в которых располагаются эти файлы;

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

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 85

танавливаются при формировании свойств проекта и сохраняются в специальном файле проекта,


обычно имеющем расширение *.vcproj.

Для работы с проектом и просмотра его компонентов следует использовать окно специального «инст-
румента» - Обозревателя решений (Solution Explorer).

Решение (Solution) в Visual Studio – это совокупность одного или нескольких проектов, образующих
одну программную разработку. Например, клиент-серверное приложение – это одно решение, состоя-
щее из двух проектов: клиентской части и серверной части.

Виды ресурсов Visual Studio


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

К ресурсам относятся:

- пиктограммы — небольшие графические изображения для применения в приложении, в том числе


значок самого приложения;

- меню — главное меню приложения и контекстные меню окон;

- строковые таблицы — таблицы для хранения строковых литералов, предназначенных, главным об-
разом, для текстов сообщений и надписей;

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

Формирование визуального графического интерфейса в редакторе ре-


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

Результат такого визуального конструирования будет записан редакторами в файлы описания ресур-
сов: в файл имя_проекта.rc и в файл resource.h, по этой причине эти файлы никогда не следует редак-
тировать «руками».

Центр «Специалист» www.specialist.ru


86 Программирование на Visual C++

Структура проекта
Структуру проекта приложения можно наглядно увидеть в окне Обозревателя решений (Solution Explor-
er) в виде дерева. Корень дерева представляет само решение, узлы первого уровня – проекты, входя-
щие в это решение. Следующий уровень – это «виртуальные» (реально не существующие) папки, слу-
жащие для логической группировки «листьев» – исходных файлов проекта:

- папка SourceFiles – исходные тексты программы на языке C++, обычно один или несколько файлов с
расширениями *.cpp, *.c;

- папка HeaderFiles –заголовочные файлы с расширением *.h;

- ResourceFiles – один или несколько файлов ресурсов с расширением *.rc и файлы пиктограмм с рас-
ширениями *.ico, *.bmp и т.п.

Отдельный «лист» на уровне виртуальных папок – файл Readme.txt, описывающий назначение файлов
проекта.

Пример – Визуальное проектирование диалогового окна

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 87

Задачи
9.1. Создайте проект Win32 (Win32 Application) с именем WinApp. Просмотрите в Обозревателе реше-
ний (Solution Explorer) набор сгенерированных мастером файлов и с помощью файла Readme.txt изучи-
те их назначение и роль в проекте. Постройте полученное приложение и исследуйте его функциональ-
ные возможности.

9.2*. Изучите содержимое файла WinApp.rc (в текстовом формате) и визуальное представление на эк-
ране монитора соответствующих ресурсов.

9.3. При помощи редактора меню добавьте в строку главного меню приложения WinApp команду «Гра-
фика», которая открывает подменю с командой «Примитивы», которая, в свою очередь, открывает
подменю с командами «Линия», «Прямоугольник», «Окружность», имеющими идентификаторы соответ-
ственно IDM_LINE, IDM_RECT, IDM_CIRCLE. Постройте проект и проверьте, что в приложении уже
поддерживается функционирование Вашего меню – подменю открываются, команды выбираются (но
пока не выполняются) и меню закрывается.

9.4. При помощи редактора диалогов добавьте в ресурсы приложения WinApp три окна диалога с на-
званиями в заголовках окон «Линия», «Прямоугольник», «Окружность», присвоив диалогам идентифи-
каторы соответственно IDD_LINE, IDD_RECT, IDD_CIRCLE. Разместите в окнах диалога поля редакти-
рования (Edit Controls) для ввода параметров геометрических объектов, присваивая полям идентифи-
каторы соответственно IDC_X1, IDC_Y1, IDC_X2, IDC_Y2 для линии и прямоугольника и IDC_X, IDC_Y,
IDC_R – для окружности. Сопроводите поля ввода подходящими надписями. Протестируйте работу
окон диалога при помощи команды редактора Формат/Проверить диалоговое окно (Format/Test dialog).

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

Вопросы для обсуждения и «углубления»


9.1. Какую роль играют файлы StdAfx.* и что такое процедура предварительной компиляции (предком-
пиляции)?

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


GUI в отдельные файлы ресурсов.

9.3. Какие еще среды разработки приложений на C++ Вы можете назвать?

9.4*. Что Вы знаете о так называемых make-файлах и их применении?

Центр «Специалист» www.specialist.ru


88 Программирование на Visual C++

Модуль 10. Структура приложения Windows

Структура приложения Windows


Структура и модель программирования консольного и Windows приложений значительно различаются:

- консольное приложение состоит минимум из одной функции main и использует императивную мо-
дель программирования;

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

Главная функция Windows-приложения WinMain


Точкой входа в Windows-приложение является функция с предопределенным идентификатором Win-
Main.

Функция WinMain решает три задачи:

- регистрация класса главного окна приложения (мастер при генерации выносит этот код в отдельную
функцию MyRegisterClass);

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

- запуск цикла обработки сообщений.

Функция WinMain всегда имеет четыре аргумента:

HINSTANCE hInstance — дескриптор (описатель) запускаемого экземпляра программы;

HINSTANCE hPrevInstance — дескриптор предыдущего экземпляра программы (для обратной со-


вместимости с Windows 3.1; в Win32 имеет нулевое значение);

LPTSTR lpCmdLine — указатель на список параметров командной строки;

int nCmdShow — режим отображения главного окна программы при запуске.

Механизм сообщений Windows, цикл обработки сообщений


Операционная система Windows взаимодействует с прикладными программами (Windows-
приложениями) посредством обмена сообщениями.

Приложение из сообщения узнает о происходящих «вокруг него» в системе событиях, в ответ на кото-
рые оно должно определенным образом реагировать. Например, пользователь нажал клавишу на кла-
виатуре или кнопку мыши и т.п.

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 89

Приложение в свою очередь, чтобы получить определенное «обслуживание» от Windows, отправляет


ей свои сообщения.

Сообщение представляет собой определенную информацию, записанную в специальную область па-


мяти. Эта область подчиняется дисциплине доступа FIFO (First In – First Out) и называется очередью
сообщений приложения (программы).

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

Функция управления окном приложения


Именно эта функция, обычно называемая оконной процедурой и именуемая WndProc, отвечает за
асинхронную обработку сообщений из очереди.

Оконная процедура никогда не вызывается из главной функции явно. Она является функцией «обрат-
ного вызова» (CALLBACK) и вызывается только самой Windows, для чего ее адрес передается Windows
при регистрации главного окна приложения.

Тело оконной процедуры чаще всего является большой инструкцией switch, в которой по числовому
коду (дескриптору) сообщения определяется программный фрагмент, ответственный за обработку это-
го сообщения. В зависимости от типа сообщения, вместе с дескриптором могут передаваться допол-
нительные параметры через аргументы wParam и/или lParam, причем часто в одной переменной-
аргументе передаются два параметра, упакованные как младшая и старшая части переменной. «Рас-
паковка» параметров в этом случае выполняется макросами LOWORD и HIWORD.

Приложение не обязано реагировать на каждое полученное сообщение – не обработанные сообщения


обычно передаются стандартному обработчику Windows.

Сообщения, посылаемые окну приложения, и их обработка


Сообщения с кодами от 0 до 0x400 – системные сообщения – зарезервированы для нужд самой Win-
dows и им директивами #define присвоены символьные имена вида WM_..., остальные коды могут
свободно использоваться программистами для собственных сообщений.

Среди системных сообщений наиболее часто используются:

WM_COMMAND – сообщение о том, что пользователь сделал выбор в меню или нажал левой кнопкой
мыши на командную кнопку;

WM_PAINT – сообщение о необходимости перерисовать окно, разрешает выводить информацию – текст


или изображение – в окно программы;

WM_DESTROY – сообщение о завершении работы приложения –это подходящее место для освобожде-
ния захваченных системных ресурсов (аналог деструктора в C++);

Центр «Специалист» www.specialist.ru


90 Программирование на Visual C++

WM_CREATE – сообщение о том, что окно приложения успешно создано, но еще не отображено на экра-
не –это подходящее место для инициализации каких-либо ресурсов или параметров (аналог конструк-
тора в C++);

WM_INITDIALOG – сообщение о том, что окно диалога успешно создано, но еще не отображено на эк-
ране –это подходящее место для инициализации содержимого этого окна (аналог конструктора в C++);

Следующая группа сообщений используется при обработке событий мыши:

WM_LBUTTONDOWN

WM_LBUTTONUP

WM_RBUTTONDOWN

WM_RBUTTONUP

WM_MOUSEMOVE

А эта группа – при обработке событий клавиатуры:

WM_CHAR

WM_SYSCHAR

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

Пример – Windows-приложение, построенное по шаблону Win32 Appli-


cation
//Listing 10.1. Пример файла WinApp.cpp Windows-приложения
// WinApp.cpp: определяет точку входа для приложения.
#include "stdafx.h"
#include "WinApp.h"
#define MAX_LOADSTRING 100

// Глобальные переменные:
HINSTANCE hInst; // текущий экземпляр
TCHAR szTitle[MAX_LOADSTRING]; // Текст строки заголовка
TCHAR szWindowClass[MAX_LOADSTRING]; // имя класса главного окна

// Отправить объявления функций, включенных в этот модуль кода:


ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(HINSTANCE hInstance,


HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
Центр «Специалист» www.specialist.ru
Программирование на Visual C++ 91

UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: разместите код здесь.
MSG msg;
HACCEL hAccelTable;

// Инициализация глобальных строк


LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_WINAPP, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// Выполнить инициализацию приложения:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}

hAccelTable=LoadAccelerators(hInstance,MAKEINTRESOURCE(IDC_WINAPP));
// Цикл основного сообщения:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
// ФУНКЦИЯ: MyRegisterClass()
//
// НАЗНАЧЕНИЕ: регистрирует класс окна.
//
// КОММЕНТАРИИ:
//
// Эта функция и ее использование необходимы только в случае, если
// нужно, чтобы данный код был совместим с системами Win32, не
// имеющими функции RegisterClassEx, которая была добавлена в
// Windows 95. Вызов этой функции важен для того, чтобы приложение
// получило "качественные" мелкие значки и установило связь с ними.

ATOM MyRegisterClass(HINSTANCE hInstance)


{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);

wcex.style = CS_HREDRAW | CS_VREDRAW;


wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance,MAKEINTRESOURCE(IDI_WINAPP));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_WINAPP);
Центр «Специалист» www.specialist.ru
92 Программирование на Visual C++

wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance,MAKEINTRESOURCE(IDI_SMALL));

return RegisterClassEx(&wcex);
}
// ФУНКЦИЯ: InitInstance(HINSTANCE, int)
//
// НАЗНАЧЕНИЕ: сохраняет обработку экземпляра и создает главное окно.
//
// КОММЕНТАРИИ:
//
// В данной функции дескриптор экземпляра сохраняется в глобальной
// переменной, а также
// создается и выводится на экран главное окно программы.

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)


{
HWND hWnd;

hInst = hInstance; // Сохранить дескриптор экземпляра в глобальной


// переменной
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
// ФУНКЦИЯ: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// НАЗНАЧЕНИЕ: обрабатывает сообщения в главном окне.
//
// WM_COMMAND - обработка меню приложения
// WM_PAINT - Закрасить главное окно
// WM_DESTROY - ввести сообщение о выходе и вернуться.

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;

switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Разобрать выбор в меню:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst,MAKEINTRESOURCE(IDD_ABOUTBOX),hWnd,About);
Центр «Специалист» www.specialist.ru
Программирование на Visual C++ 93

break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: добавьте любой код отрисовки...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// Обработчик сообщений для окна "О программе".
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;

case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}

Задачи
10.1. Изучите, где и как в листинге 10.1. поддерживается функционирование окна диалога «О програм-
ме». По аналогии реализуйте решение задач 9.3.-9.5*. Соответствующие диалоговые оконные проце-
дуры назовите LineDlgProc, RectDlgProc, CircleDlgProc и (пока) реализуйте их аналогично
процедуре About.

10.2*. В оконных процедурах создайте (пока статические) переменные для считывания значений из по-
лей ввода окон диалога. В ответ на нажатие кнопки ОК реализуйте считывание введенных значений (а
для контроля – вывод значений переменных в соответствующие поля окна диалога в ответ на сообще-
ние WM_INITDIALOG). Например, для линии:

Центр «Специалист» www.specialist.ru


94 Программирование на Visual C++

чтение координаты x1: x1 = GetDlgItemInt(hDlg, IDC_X1, 0, 0);

запись координаты x1 в поле: SetDlgItemInt(hDlg, IDC_X1, x1, 0);

Протестируйте Ваш код, несколько раз открывая окна диалога и вводя в них данные.

10.3**. Вернитесь к разработанным Вами классам графических примитивов (например, в задаче 6.2.),
добавьте в проект WinApp их файлы *.h и *.cpp. Для проверки их работоспособности (пока без рисо-
вания) добавьте в код обработчика сообщения WM_PAINT фрагмент наподобие следующего:

{
using namespace std;
strstream ss;

Line ab(Point(10,10),Point(30,40));
ab.Print(ss << "Line ab: ") << " created" << endl;

Line cd(Point(30,40),Point(80,10));
ab.Print(ss << "Line cd: ") << " created" << endl;

ss << ends;
RECT rt;
GetClientRect(hWnd, &rt);
DrawTextA(hdc, ss.str(), -1, &rt, DT_EXPANDTABS|DT_WORDBREAK);
}

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

10.4***. Примените в задаче 10.3**. Ваши результаты решения задач 7.1.,7.3*. и 8.3. или 7.2.,7.3*. и 8.3.

10.5***. Создайте в задаче 10.4***. контейнер(ы) объектов статическим(и) в файле и пусть в задаче
10,2*, в оконных процедурах создаются объекты и добавляются в этот(эти) контейнер(ы).

10.6**. Протестируйте Ваше решение задачи 8.4*. подобно решению задачи 10.4***.

10.7**. Примените решение задач 9,1,-9.3*., 10.1,-10,2*. и 10.5***.,10.6**. к задаче 8.4*.

10.8**. Протестируйте Ваше решение задачи 8.5*. подобно решению задачи 10.4***.

Вопросы для обсуждения и «углубления»


10.1*. По какой причине в Windows-приложениях функция WinMain никогда сама не вызывает оконную
процедуру WndProc?

10.2. Почему в листинге 10.1. функция WinMain именуется как _tWinMain?

10.3*. Что за «странные» наименования типов используются в листинге Windows-приложения (да еще
написанные прописными буквами)?

10.4. Что такое «венгерская нотация», используемая в коде Windows-приложений?

10.5. Что такое Win32 API?

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 95

10.6. Сформулируйте принципы событийной модели программирования.

10.7. Что такое синхронная и асинхронная обработка сообщений?

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

10.9**. Все элементы пользовательского интерфейса в Windows делятся на две категории. К первой от-
носятся довольно «старые» элементы – их поддержка «встроена» в саму Windows. Для работы с эле-
ментами второй – так называемыми «общими элементами управления» (common controls) – требуется
подключение и инициализация специальной библиотеки поддержки. Познакомьтесь с примерами рабо-
ты с этой библиотекой.

10.10. Какие библиотеки для разработки GUI-программ вы можете назвать? А охарактеризовать их осо-
бенности?

Центр «Специалист» www.specialist.ru


96 Программирование на Visual C++

Модуль 11. Графика под Windows

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

шрифт – для вывода текста,

перо – для рисования линий,

кисть – для закрашивания замкнутых областей и т.д.

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

Каждая функция вывода информации имеет обязательный аргумент – дескриптор контекста устрой-
ства hDC (handle of Device Context).

Обработка сообщения WM_PAINT


Приложение обычно выводит информацию в ответ на получение сообщения WM_PAINT. Перед началом
операций вывода следует запросить дескриптор контекста hDC (это системный ресурс) вызовом функ-
ции BeginPaint. После окончания вывода необходимо вернуть ресурс hDC, вызвав функцию
EndPaint. Все операции вывода выполняются между этими двумя вызовами. (Вне сообщения
WM_PAINT вместо BeginPaint и EndPaint используются функции GetDC и ReleaseDC.)

Вывод графических образов (примитивов)


Для вывода графической информации (в том числе текста) в Win32 API имеется большой набор функ-
ций. Некоторые из них показаны далее в примере.

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

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


рисования и восстановления контекста все объекты, что были созданы функциями Create…, должны
быть удалены вызовом функции DeleteObject.

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 97

Битовые образы
Файл изображения *.bmp загружается функцией LoadBitmap, выбирается в контекст, созданный в па-
мяти функцией CreateCompatibleDC, откуда функциями быстрой пересылки блоков битов BitBlt,
StretchBlt и т.п., выводится (целиком или частично) в контекст устройства вывода. Заметьте, что пе-
ред окончанием работы приложения, память, занятая загруженным изображением, должна быть осво-
бождена функцией DeleteObject.

Пример – Вывод графических примитивов


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

//Listing 11.1. Фрагмент оконной процедуры WndProc – вывод графики


// ...
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// ...
static int x,y;
static HBITMAP hBit;
switch (message)
{
case WM_CREATE:
hBit=LoadBitmap(hInst,(LPCTSTR)IDB_BITMAP1);
break;
// ...
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: добавьте любой код отрисовки...
{
//Пикселы
SetPixel(hdc,50,0,RGB(255,0,0));
SetPixel(hdc,55,55,RGB(50,50,0));

//Работа с пером
//создание сплошного красного пера толщиной 3 пиксела
HPEN hNewPen = CreatePen(PS_SOLID,3,RGB(255,0,0));

/* Pen styles
PS_SOLID Pen is solid.
Next styles are valid only when the pen width is one or less in
device units.
PS_DASH Pen is dashed.
PS_DOT Pen is dotted.
PS_DASHDOT Pen has alternating dashes and dots.
PS_DASHDOTDOT Pen has alternating dashes and double dots.
PS_NULL Pen is invisible.
*/

Центр «Специалист» www.specialist.ru


98 Программирование на Visual C++

//выбор нового пера в контекст устройства с сохранением


//старого пера
HPEN hOldPen = (HPEN)SelectObject(hdc,hNewPen);

//Линии
MoveToEx(hdc,60,60,NULL);
LineTo(hdc,120,120);
LineTo(hdc,240,60);
//Работа с кистью - сплошная желтая кисть
HBRUSH hNewBrush = CreateSolidBrush(RGB(255,255,0));
HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc,hNewBrush);
//Прямоугольник (определяется левой верхней и
//правой нижней угловыми точками
Rectangle(hdc, 120,130,270,230);
//штриховая кисть
HBRUSH hNewBrush2 = CreateHatchBrush(HS_DIAGCROSS,
RGB(0,127,0));
SelectObject(hdc,hNewBrush2);

/* Hatch styles
HS_BDIAGONAL 45-degree downward left-to-right hatch
HS_CROSS Horizontal and vertical crosshatch
HS_DIAGCROSS 45-degree crosshatch
HS_FDIAGONAL 45-degree upward left-to-right hatch
HS_HORIZONTAL Horizontal hatch
HS_VERTICAL Vertical hatch
*/
//Эллипс (вписывается в указанный прямоугольник)
Ellipse(hdc, 120,130,270,230);
//стандартная кисть Windows
SelectObject(hdc,GetStockObject(LTGRAY_BRUSH));
//Сектор (часть эллипса между двумя выходящими из
//центра лучами, каждый луч задается точкой, через
//которую он проходит)
Pie(hdc,350,120,500,240,600,220,550,50);
SelectObject(hdc, hOldBrush);
SelectObject(hdc, hOldPen);
DeleteObject(hNewBrush2);
DeleteObject(hNewBrush);
DeleteObject(hNewPen);
}
EndPaint(hWnd, &ps);
break;
case WM_RBUTTONDOWN:
x=LOWORD(lParam);
y=HIWORD(lParam);
{
hdc=GetDC(hWnd);
HDC memdc=CreateCompatibleDC(hdc);
SelectObject(memdc,hBit);
BitBlt(hdc,x,y,48,48,memdc,0,0,SRCCOPY);
ReleaseDC(hWnd,hdc);
Центр «Специалист» www.specialist.ru
Программирование на Visual C++ 99

DeleteDC(memdc);
}
break;
case WM_DESTROY:
DeleteObject(hBit);
PostQuitMessage(0);
break;
// ...
}

Задачи
11.1. В задаче 10.5***. визуализируйте объекты, реализовав в классах код метода Draw.

11.2. В задаче 10.8**. визуализируйте работу светофора.

11.3**. Какие элементы пользовательского интерфейса Вам бы потребовались в задаче 10.7**.? Обра-
титесь к вопросу10.9*. и примените их на практике.

11.4*. Рассмотрите возможность реализации библиотеки «деловой графики» для рисования столбчатых
и круговых диаграмм, графиков функций и т.д.

Вопросы для обсуждения и «углубления»


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

11.2. В Win32 API есть функции, позволяющие изменить используемую систему координат, выполнять
преобразования координат, работать с разными цветовыми моделями и многое другое…

11.1. Какие еще библиотеки для разработки графических приложений вы можете назвать? А охаракте-
ризовать их особенности?

Центр «Специалист» www.specialist.ru


100 Программирование на Visual C++

Модуль 12. Итоговое занятие


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

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

графическое приложение «Маленький графический редактор»

«Планировщик» (или «Ежедневник» - Organizer)

«Светофор» (TrafficLights)

Но Вы могли бы взять для себя и любую другую интересную для Вас тему!

Вам в помощь в приложении приводятся еще два листинга – пример рисования мышью линии методом
«резиновой нити» и использование поддержки в Windows некоторых «стандартных диалогов», в том
числе выбор шрифта и цвета, открытие и сохранение файла. Примеры взяты из MSDN и слегка адап-
тированы.

Желаем удачи!

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 101

Приложение

Пример – рисование мышью


//Listing 12.1. Фрагмент оконной процедуры WndProc – рисование мышью
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// ...
RECT rcClient; // client area rectangle
POINT ptClientUL; // client upper left corner
POINT ptClientLR; // client lower right corner
static POINTS ptsBegin; // beginning point
static POINTS ptsEnd; // new endpoint
static POINTS ptsPrevEnd; // previous endpoint
static BOOL fPrevLine = FALSE; // previous line flag
switch (message)
{
case WM_LBUTTONDOWN:
// Capture mouse input.
SetCapture(hWnd);
// Retrieve the screen coordinates of the client area,
// and convert them into client coordinates.
GetClientRect(hWnd, &rcClient);
ptClientUL.x = rcClient.left;
ptClientUL.y = rcClient.top;
// Add one to the right and bottom sides, because the
// coordinates retrieved by GetClientRect do not
// include the far left and lowermost pixels.
ptClientLR.x = rcClient.right + 1;
ptClientLR.y = rcClient.bottom + 1;
ClientToScreen(hWnd, &ptClientUL);
ClientToScreen(hWnd, &ptClientLR);

// Copy the client coordinates of the client area


// to the rcClient structure. Confine the mouse cursor
// to the client area by passing the rcClient structure
// to the ClipCursor function.
SetRect(&rcClient, ptClientUL.x, ptClientUL.y,
ptClientLR.x, ptClientLR.y);
ClipCursor(&rcClient);
// Convert the cursor coordinates into a POINTS
// structure, which defines the beginning point of the
// line drawn during a WM_MOUSEMOVE message.
ptsBegin = MAKEPOINTS(lParam);
SetCursor(LoadCursor(NULL,IDC_CROSS));
break;

case WM_MOUSEMOVE:
// When moving the mouse, the user must hold down
// the left mouse button to draw lines.
Центр «Специалист» www.specialist.ru
102 Программирование на Visual C++

if (wParam & MK_LBUTTON)


{
// Retrieve a device context (DC) for the client area.
hdc = GetDC(hWnd);
HPEN hPen = CreatePen(PS_SOLID,1,RGB(0,0,0));
HPEN hOldPen=(HPEN)SelectObject(hdc,hPen);
// The following function ensures that pixels of
// the previously drawn line are set to white and
// those of the new line are set to black.
SetROP2(hdc, R2_NOTXORPEN); //указание: закомментируйте
//и объясните последствия!!
//потом раскомментируйте
// If a line was drawn during an earlier WM_MOUSEMOVE
// message, draw over it. This erases the line by
// setting the color of its pixels to white.
if (fPrevLine)
{
MoveToEx(hdc, ptsBegin.x, ptsBegin.y,
(LPPOINT) NULL);
LineTo(hdc, ptsPrevEnd.x, ptsPrevEnd.y);
}
// Convert the current cursor coordinates to a
// POINTS structure, and then draw a new line.
ptsEnd = MAKEPOINTS(lParam);
MoveToEx(hdc, ptsBegin.x, ptsBegin.y, (LPPOINT) NULL);
LineTo(hdc, ptsEnd.x, ptsEnd.y);

// Set the previous line flag, save the ending


// point of the new line, and then release the DC.
fPrevLine = TRUE;
ptsPrevEnd = ptsEnd;
SelectObject(hdc,hOldPen);
DeleteObject(hPen);
ReleaseDC(hWnd, hdc);
}
break;
case WM_LBUTTONUP:

// The user has finished drawing the line. Reset the


// previous line flag, release the mouse cursor, and
// release the mouse capture.
fPrevLine = FALSE;
ClipCursor(NULL);
ReleaseCapture();
SetCursor(LoadCursor(NULL,IDC_ARROW));
break;
// ...
}
// ...
}

Центр «Специалист» www.specialist.ru


Программирование на Visual C++ 103

Пример – поддержка стандартных диалогов


Для работы этого примера требуется добавить в файл StdAfx.h строки

#include <commdlg.h>
#include <fstream>

//Listing 12.2. Фрагмент WndProc – поддержка стандартных диалогов


LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// ...
static DWORD rgbCurrent; // initial color selection
static HFONT hFont;
static char szHello[MAX_LOADSTRING]="Hello, World!";

switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
//предполагается, что в меню есть такие команды ...
case IDM_INSTALLED_FONTS:
{
CHOOSEFONT cf; // common dialog box structure
LOGFONT lf; // logical font structure
TCHAR szFontStyle[60];
// Initialize CHOOSEFONT
ZeroMemory(&cf, sizeof(CHOOSEFONT));
cf.lStructSize = sizeof (CHOOSEFONT);
cf.hwndOwner = hWnd;
cf.lpLogFont = &lf;
cf.rgbColors = rgbCurrent;
cf.lpszStyle = szFontStyle;
cf.Flags = CF_SCREENFONTS|CF_EFFECTS|CF_USESTYLE;

if(ChooseFont(&cf)==TRUE)
{
if(hFont) DeleteObject(hFont);
hFont = CreateFontIndirect(cf.lpLogFont);
rgbCurrent= cf.rgbColors;
InvalidateRect(hWnd,0,1);
}
}
break;
case IDM_COLOR:
{
CHOOSECOLOR cc; // common dialog box structure
static COLORREF acrCustClr[16];//custom clrs array
// Initialize CHOOSECOLOR
ZeroMemory(&cc, sizeof(CHOOSECOLOR));
cc.lStructSize = sizeof(CHOOSECOLOR);
Центр «Специалист» www.specialist.ru
104 Программирование на Visual C++

cc.hwndOwner = hWnd;
cc.lpCustColors = (LPDWORD) acrCustClr;
cc.rgbResult = rgbCurrent;
cc.Flags = /*CC_FULLOPEN | */CC_RGBINIT;

if(ChooseColor(&cc)==TRUE)
{
rgbCurrent = cc.rgbResult;
InvalidateRect(hWnd,0,1);
}
}
break;
case IDM_FILEOPEN:
{
OPENFILENAME ofn; // common dialog box structure
TCHAR szFile[260]=L"*.*"; // buffer for file name
// Initialize OPENFILENAME
ZeroMemory(&ofn, sizeof(OPENFILENAME));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hWnd;
ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter = L"All\0*.*\0Text\0*.TXT\0";
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
// Display the Open dialog box.
if(GetOpenFileName(&ofn)==TRUE)
{
std::ifstream file;
file.open(ofn.lpstrFile);
if(!file)
{
MessageBox(hWnd,ofn.lpstrFile,
L"Open File Error",MB_OK|MB_ICONHAND);
}
else
{
file.getline(szHello,sizeof(szHello));
file.close();
InvalidateRect(hWnd,0,true);
}
}
}
break;

case IDM_FILESAVEAS:
{
OPENFILENAME ofn; // common dialog box structure
TCHAR szFile[260]=L"*.*"; // buffer for file name

// Initialize OPENFILENAME
ZeroMemory(&ofn, sizeof(OPENFILENAME));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hWnd;
Центр «Специалист» www.specialist.ru
Программирование на Visual C++ 105

ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter = L"All\0*.*\0Text\0*.TXT\0";
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
// Display the Open dialog box.
if(GetSaveFileName(&ofn)==TRUE)
{
std::ofstream file;
file.open(ofn.lpstrFile);
if(!file)
{
MessageBox(hWnd,ofn.lpstrFile,
L"Save File Error",MB_OK|MB_ICONHAND);
}
else
{
file << szHello;
file.close();
}
}
}
break;
}
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
{
RECT rt;
GetClientRect(hWnd, &rt);
HFONT hFontPrev = (HFONT)SelectObject(hdc, hFont);
DWORD rgbPrev = SetTextColor(hdc, rgbCurrent);
DrawTextA(hdc, szHello, strlen(szHello), &rt,
DT_CENTER|DT_VCENTER|DT_SINGLELINE);
SetTextColor(hdc, rgbPrev);
SelectObject(hdc, hFontPrev);
}
EndPaint(hWnd, &ps);
break;

case WM_DESTROY:
if(hFont) DeleteObject(hFont);
PostQuitMessage(0);
break;
// ...
}
// ...
}

Центр «Специалист» www.specialist.ru


106 Программирование на Visual C++

Литература
1. Страуструп, Б. Программирование: принципы и практика использования C++, испр. изд. – М.: ООО
«И.Д. Вильямс», 2011.

2. Страуструп, Б. Язык программирования C++. Специальное издание. – М.-СПб: Бином, Невский


диалект, 2006.

3. Страуструп, Б. Дизайн и эволюция C++. – М.-СПб: ДМК Пресс, Питер, 2006.

4. Саттер Г., Александреску А. Стандарты программирования на C++. – М.: Вильямс, 2005.

5. Мейерс С. Эффективное использование C++. 55 верных советов улучшить структуру и код ваших
программ. – М.: ДМК Пресс, 2006.

6. Мейерс С. Наиболее эффективное использование C++. 35 новых рекомендаций по улучшению


ваших программ и проектов. – М.: ДМК Пресс, 2000.

7. Саттер Г. Решение сложных задач на C++. – М.: Вильямс, 2002.

8. Саттер Г. Новые сложные задачи на C++. – М.: Вильямс, 2005.

9. Мейерс С. Эффективное использование STL. Библиотека программиста. – СПб: Питер, 2002.

10. Плаугер П., Степанов А., Ли М., Массер Д. STL – стандартная библиотека шаблонов C++. СПб:
БХВ-Петербург, 2004.

11. Элджер Дж. C++: библиотека программиста. – Спб: Питер, 2000.

12. Шилдт Г. Полный справочник по C++. 4-е изд. – М.: Вильямс, 2006.

13. Шилдт Г. C++: базовый курс. 3-е изд. – М.: Вильямс, 2010.

14. Дейтел Х., Дейтел П. Как программировать на C++. 3-е изд. М.: Бином, 2001.

15. Прата С. Яэык программирования C++. Лекции и упражнения. 6-е изд. – М.: Вильямс, 2011.

16. Румянцев П.В. Азбука программирования в Win32 API. 4-е изд. – М.: Горячая линия – Телеком,
2004.

Центр «Специалист» www.specialist.ru