
- •Кетков ю.Л.
- •Раздел 5. Системные данные текстового типа 33
- •Раздел 6. Основные синтаксические конструкции языка c 46
- •Раздел 7. Указатели и ссылки 59
- •Раздел 8. Функции и их аргументы 62
- •Раздел 9. Работа с массивами. 74
- •Раздел 10. Пользовательские типы данных. 95
- •Раздел 11. Работа с файлами 104
- •Раздел 12. Библиотеки стандартных и нестандартных функций 118
- •Раздел 15. Классы. Создание новых типов данных 131
- •Раздел 16. Классы как средство создания больших программных комплексов 150
- •Раздел 17. Прерывания, события, обработка исключений 167
- •Введение
- •Раздел 1. Немного истории
- •Раздел 2. Структура программы на языке c
- •Раздел 3. Среда программирования
- •Раздел 4. Системные данные числового типа
- •4.1. Типы числовых данных и их представление в памяти эвм
- •4.1.1. Внутреннее представление целочисленных данных
- •4.1.2. Однобайтовые целочисленные данные
- •4.1.3. Двухбайтовые целочисленные данные
- •4.1.4. Четырехбайтовые целочисленные данные
- •4.1.5. Восьмибайтовые целочисленные данные
- •4.2. Внутреннее представление данных вещественного типа
- •4.3. Внешнее представление числовых констант
- •4.4. Объявление и инициализация числовых переменных
- •4.5. Ввод числовых данных по запросу программы
- •4.5.1. Потоковый ввод данных числового типа
- •4.5.2. Форматный ввод
- •4.6. Вывод числовых результатов
- •4.6.1. Форматный вывод
- •4.6.2. Потоковый вывод
- •4.7. Примеры программ вывода числовых данных
- •4.8. Операции над числовыми данными целого типа
- •4.9. Операции над числовыми данными вещественного типа
- •Раздел 5. Системные данные текстового типа
- •5.1. Символьные данные и их представление в памяти эвм
- •5.2. Строковые данные и их представление в памяти эвм
- •5.3. Ввод текстовых данных во время работы программы
- •5.3.1. Форматный ввод
- •5.3.3. Потоковый ввод
- •5.3.4. Специальные функции ввода текстовых данных
- •5.4. Вывод текстовых данных
- •5.4.1. Форматный вывод
- •5.5.2. Операции над строковыми данными
- •5.6. Управление дисплеем в текстовом режиме
- •Раздел 6. Основные синтаксические конструкции языка c
- •6.1. Заголовок функции и прототип функции
- •6.2. Объявление локальных и внешних данных
- •6.3. Оператор присваивания
- •6.4. Специальные формы оператора присваивания
- •6.5. Условный оператор
- •6.6. Оператор безусловного перехода
- •6.7. Операторы цикла
- •6.8. Дополнительные операторы управления циклом
- •6.9. Оператор выбора (переключатель)
- •6.10. Обращения к функциям
- •6.11. Комментарии в программах
- •Раздел 7. Указатели и ссылки
- •7.1. Объявление указателей
- •7.2. Операции над указателями
- •7.3. Ссылки
- •Раздел 8. Функции и их аргументы
- •8.1. Параметры-значения
- •8.2. Параметры-указатели
- •8.3. Параметры-ссылки
- •8.4. Параметры-константы
- •8.5. Параметры по умолчанию
- •8.6. Функции с переменным количеством аргументов
- •8.7. Локальные, глобальные и статические переменные
- •8.8. Возврат значения функции
- •8.9. Рекурсивные функции
- •8.10. Указатели на функцию и передача их в качестве параметров
- •8.11. "Левые" функции
- •Раздел 9. Работа с массивами.
- •9.1. Объявление и инициализация массивов.
- •9.2. Некоторые приемы обработки числовых массивов
- •9.2. Программирование задач линейной алгебры
- •9.2.1. Работа с векторами
- •9.2.2.Работа с матрицами
- •9.3. Поиск
- •9.3.1. Последовательный поиск
- •9.3.2. Двоичный поиск
- •9.4. Сортировка массивов.
- •9.4.1. Сортировка методом пузырька
- •9.4.2. Сортировка методом отбора
- •9.4.3. Сортировка методом вставки
- •9.4.4. Сортировка методом Шелла
- •9.4.5.Быстрая сортировка
- •9.5. Слияние отсортированных массивов
- •9.6. Динамические массивы.
- •Раздел 10. Пользовательские типы данных.
- •10.1. Структуры
- •10.1.1. Объявление и инициализация структур
- •10.1.2. Структуры – параметры функций
- •10.1.3.Функции, возвращающие структуры
- •10.2. Перечисления
- •10.3. Объединения
- •Раздел 11. Работа с файлами
- •11.1.Файлы в операционной системе
- •11.1. Текстовые (строковые) файлы
- •11.2. Двоичные файлы
- •11.3. Структурированные файлы
- •11.4. Форматные преобразования в оперативной памяти
- •11.5. Файловые процедуры в системе bcb
- •11.5.1. Проверка существования файла
- •11.5.2. Создание нового файла
- •11.5.3. Открытие существующего файла
- •11.5.4. Чтение из открытого файла
- •11.5.5. Запись в открытый файл
- •11.5.6. Перемещение указателя файла
- •11.5.7. Закрытие файла
- •11.5.8. Расчленение полной спецификации файла
- •11.5.9. Удаление файлов и пустых каталогов
- •11.5.10. Создание каталога
- •11.5.11. Переименование файла
- •11.5.12. Изменение расширения
- •11.5.13. Опрос атрибутов файла
- •11.5.14. Установка атрибутов файла
- •11.5.15. Опрос и изменение текущего каталога
- •11.6. Поиск файлов в каталогах
- •Раздел 12. Библиотеки стандартных и нестандартных функций
- •12.2. Организация пользовательских библиотек
- •12.3. Динамически загружаемые библиотеки
- •13.1. Препроцессор и условная компиляция
- •13.2. Компилятор bcc.Exe
- •13.3. Утилита grep.Com поиска в текстовых файлах
- •14.1. Переопределение (перегрузка) функций
- •14.2. Шаблоны функций
- •Раздел 15. Классы. Создание новых типов данных
- •15.1. Школьные дроби на базе структур
- •15.2. Школьные дроби на базе классов
- •15.3. Класс на базе объединения
- •15.4. Новые типы данных на базе перечисления
- •15.5. Встраиваемые функции
- •15.6. Переопределение операций (резюме)
- •15.8. Конструкторы и деструкторы (резюме)
- •Раздел 16. Классы как средство создания больших программных комплексов
- •16.1. Базовый и производный классы
- •16.1.1.Простое наследование
- •16.1.2. Вызов конструкторов и деструкторов при наследовании
- •16.1.3. Динамическое создание и удаление объектов
- •16.1.4. Виртуальные функции
- •16.1.5. Виртуальные деструкторы
- •16.1.6. Чистые виртуальные функции и абстрактные классы
- •16.2. Множественное наследование и виртуальные классы
- •16.3. Объектно-ориентированный подход к созданию графической системы
- •Раздел 17. Прерывания, события, обработка исключений
- •17.1. Аппаратные и программные прерывания
- •17.2. Исключения
16.2. Множественное наследование и виртуальные классы
О множественном наследовании говорят в тех случаях, когда в создании производного класса участвуют два или более родителей:
class B1 {//первый базовый класс
int x;
public:
B1(int n):x(n) {cout<<"Init_B1"<<endl;} //конструктор B1
int get_x(){return x;}
~B1() {cout<<"Destr_B1"<<endl;} //деструктор B1
};
class B2 {//второй базовый класс
int y;
public:
B2(int n):y(n) {cout<<"Init_B2"<<endl;} // конструктор B2
int get_y(){return y;}
~B2() {cout<<"Destr_B2"<<endl;} //деструктор B2
};
class D: public B1, public B2 {
int z;
public:
D(int a,int b,int c):B1(a),B2(b),z(c)
{cout<<"Init_D"<<endl;} //конструктор D
void show() {cout<<"x="<<get_x()<<" y="<<get_y()<<" z="<<z<<endl;}
~D() {cout<<"Destr_D"<<endl;} //деструктор D
};
#include <iostream.h>
void main()
{ D qq(1,2,3);
qq.show();
}
//=== Результат работы ===
Init_B1
Init_B2
Init_D
x=1 y=2 z=3
Destr_D
Destr_B2
Destr_B1
Последовательность обращений к конструкторам родительских классов определяется очередностью их вызовов в списке инициализации. Если таковой отсутствует, то определяющим является порядок перечисления родителей в объявлении производного класса.
При множественном наследовании может возникнуть некоторая неопределенность, связанная с тем, что родительские данные могут попытаться попасть в производный класс несколькими путями. Например, классы A и B являются родителями класса C. Если в формировании класса D участвуют классы A и C, то данные-потомки класса A попадают в класс D и прямым путем, и в составе наследства класса C. И тогда перед компилятором возникает неразрешимая проблема – с какой веточкой унаследованных данных надо работать и методы какого класса надо вызывать. Для разрешения такой двойственности класс A должен быть объявлен виртуальным:
class B: virtual public A {
...//описание класса B
};
class C: virtual public A, public B {
//описание класса C
};
При наследовании от виртуального класса производный класс вместо родительских данных получает ссылки на эти данные, что позволяет предотвратить дублирование наследства.
16.3. Объектно-ориентированный подход к созданию графической системы
Создание достаточно универсальной графической системы – это серьезный проект, требующий разработки большого количества разнообразных процедур. В их состав помимо средств объявления графических примитивов (точки, отрезки прямых, дуги окружностей, прямоугольники, пояснительные подписи и т.п.) и манипуляций с объектами (отображение на экране, стирание, перекраска, перемещение) должны входить различные вспомогательные утилиты. Например, такие как запоминание и восстановление фрагментов изображения, процедуры анимации, аппроксимации и сглаживания кривых, заливки и штриховки замкнутых областей, пересечения полигонов и многое другое.
Поэтому мы ограничимся лишь демонстрацией простейшей графической системы, имеющей в своем распоряжении минимальное число графических объектов – точки, окружности и залитые окружности. Эти объекты можно будет создавать в оперативной памяти, отображать на экране, делать невидимыми и перемещать по экрану в заданное место. Более того, для манипуляций с этими объектами мы воспользуемся существующей в среде BC 3.1 библиотекой процедур BGI (Borland Graphics Interface), обеспечивающих перевод экрана в простейший графический режим (режим VGA с разрешением 640×480) и отображение на нем графических примитивов. Однако детали работы с этой библиотекой мы постараемся скрыть от пользователя. Основная цель нашей демонстрации – показать главные аспекты объектно-ориентированного подхода на достаточно наглядном примере.
Описания наших новых классов, методов и вспомогательных утилит мы разместим в файле с именем gs.h (от Graphics System). По аналогии с работой с файлами нам понадобятся процедуры открытия (инициализации) графической системы и ее закрытия. Для этого мы включим в файл gs.h следующий фрагмент:
#include <graphics.h>
int gs;
void open_gs()
{ int gd=0,gm;
initgraph(&gd,&gm,"");
gs=1;
}
void close_gs()
{ closegraph(); gs=0; }
Заголовочный файл graphics.h содержит заголовки функций и описания констант библиотеки BGI. Переменная gs, может быть, понадобится в будущем для индикации готовности графической системы к работе (при gs=1 система открыта для работы с графическими объектами, закрытие системы сопровождается засылкой нуля в переменную gs). Для приведения библиотеки BGI в состояние готовности используется процедура initgraph и графический драйвер egavga.bgi, который мы из соображений удобства разместим в своем текущем каталоге. Восстановление текстового режима работы дисплея осуществляется процедурой closegraph из библиотеки BGI. Однако пользователь о деталях работы с процедурами BGI ничего знать не должен. Для "открытия" графической системы он должен обратиться к процедуре open_gs, а для закрытия – к процедуре close_gs (почти полная аналогия открытия и закрытия файлов).
Описание нашей графической системы мы начнем с абстрактного класса GO (от Graphics Object).
class GO {
protected:
int x,y,is_v,fc,bc;
public:
GO():x(0),y(0),is_v(0),bc(15),fc(0)
{ setcolor(fc);setbkcolor(bc); }
GO(int x1,int y1,int c=0):x(x1),y(y1),is_v(0),fc(c),bc(15)
{ setcolor(fc);setbkcolor(bc); }
virtual void hide()=0;
virtual void show()=0;
void move(int x1,int y1);
};
Защищенными данными в этом классе являются:
x,y – целочисленные координаты точки привязки графического объекта в системе координат экрана (для объекта "точка" это координаты точки, для окружности – координаты центра);
is_v – индикатор видимости (видимому на экране объекту соответствует is_v=1);
fc – цвет рисования (целое число из диапазона [0,15]);
bc – цвет фона (целое число из диапазона [0,15]).
Конструктор по умолчанию считает, что точкой привязки графического объекта является начало координат (верхний левый угол экрана). С помощью процедуры setcolor устанавливается черный цвет рисования (fc=0), а с помощью процедуры setbkcolor – белый цвет фона (bc=15).
В классе GO объявлены два чисто виртуальных метода – hide (стереть изображение объекта) и show (отобразить объект). Метод move осуществляет перемещение объекта в новую точку привязки и не является виртуальным. Поэтому мы его определим за пределами описания класса:
void GO::move(int x1,int y1)
{ hide(); //стереть прежнее изображение объекта
x=x1; y=y1; //изменить координаты точки привязки
show(); //отобразить объект в новом месте
}
Теперь определим производный класс point, с помощью которого вводятся объекты типа "точка" и манипуляции с объектами этого типа. Новый класс наследует от класса GO все данные (повторять их в классе point не надо). Конструкторы класса point явно вызывают конструкторы родителя, передавая им в случае необходимости недостающие параметры.
class point: public GO {
public:
point():GO() {}
point(int x1,int y1,int c=0):GO(x1,y1,c) {}
void hide();
void show();
};
В классе point переопределяются наследуемые виртуальные методы. Для стирания изображения видимой точки используется процедура putpixel, которая "рисует" точку цветом фона. Для отображения невидимой точки используется та же процедура с заданным значением цвета.
void point::hide() //стирание точки
{ if(is_v) { putpixel(x,y,bc); is_v=0; } }
void point::show() //отображение точки
{ if(!is_v) { putpixel(x,y,fc); is_v=1; } }
Для перемещения точки сохраняется родительская процедура move, которая теперь обращается не к виртуальным, а реальным методам класса point – hide и show.
Добавим класс circ, производный от класса GO и предназначенный для работы с объектами типа "окружность". В дополнение к данным, унаследованным от родителя, здесь понадобится еще и радиус окружности (переменная r)
class circ: public GO {
int r;
public:
circ():GO(),r(1){ }
circ(int x1,int y1,int r1,int c=0): GO(x1,y1,c),r(r1) { }
void hide();
void show();
};
Унаследованные виртуальные методы hide и show здесь также придется переопределить. Для стирания видимой окружности используем процедуру построения объекта, задав в качестве цвета рисования цвет фона.
void circ::hide() //стирание окружности
{ if(is_v==0) return;
int fc1=getcolor(); //запоминание цвета рисования
setcolor(bc); //замена цвета рисования на цвет фона
circle(x,y,r); //построение окружности
setcolor(fc1); //восстановление цвета рисования
is_v=0;
}
void circ::show() //отображение окружности
{ if(is_v) return;
int fc1=getcolor(); //запоминание цвета рисования
setcolor(fc); //замена на цвет объекта
circle(x,y,r); //построение окружности
setcolor(fc1); //восстановление цвета рисования
is_v=1;
}
Класс circf для работы с залитыми окружностями тоже образуем из класса GO.
class circf: public GO {
int r;
public:
circf():GO(),r(1){}
circf(int x1,int y1,int r1,int c=0):r(r1),GO(x1,y1,c) {}
void show();
void hide();
};
Для реализации метода show воспользуемся процедурой построения залитого эллипса – fillellipse. Но предварительно потребуется задать шаблон заливки, соответствующий сплошному заполнению замкнутой области (графическая константа SOLID_FILL=1), и цвет заливки, равный цвету объекта (значение переменной fc). Обе эти установки выполняются библиотечной процедурой setfillstyle.
void circf::show()
{ if(is_v) return;
setfillstyle(1,fc); //установка стиля и цвета заливки
fillellipse(x,y,r,r); //построение залитой окружности
is_v=1;
}
Метод hide оказался не совсем тривиальным, т.к. после построения эллипса, залитого цветом фона, сохраняется цветная граница. Поэтому приходится выполнить еще одно построение – нарисовать контуры окружности цветом фона.
void circf::hide()
{ if(!is_v) return;
setfillstyle(1,bc); //установка стиля и цвета заливки
fillellipse(x,y,r,r); //стирание залитой окружности
setcolor(bc); //замена цвета рисования на цвет фона
circle(x,y,r); //стирание границы окружности
setcolor(fc); //восстановление цвета рисования
is_v=0;
}
А теперь настало время апробировать нашу графическую систему. Если забыть о содержимом файла gs.h и о времени, затраченном на его создание, то работа с допустимым набором графических объектов выглядит абсолютно прозрачно. После каждой графической манипуляции организована пауза в работе программы до нажатия любой клавиши. Это дает возможность рассмотреть на экране результат очередной операции.
#include "gs.h"
#include <conio.h>
void main()
{ open_gs(); //открытие графической системы
//Объявление графических объектов
point P1(21,10,2); //зеленая точка (21,10)
circ C1(21,50,20,4); //красная окружность радиуса 20
circf CF1(21,100,20,12); //залитая окружность
//Отображение графических объектов
P1.show(); getch(); //показ точки
C1.show(); getch(); //показ окружности
CF1.show(); getch(); //показ залитой окружности
//Перемещение графических объектов
P1.move(121,10); getch(); //сдвиг точки
C1.move(121,50); getch(); //сдвиг окружности
CF1.move(121,100); getch(); //сдвиг залитой окружности
//Стирание графических объектов
P1.hide(); getch(); //стирание точки
C1.hide(); getch(); //стирание окружности
CF1.hide(); getch(); //стирание залитой окружности
// Перемещение графических объектов
P1.move(221,10); getch(); //сдвиг точки
C1.move(221,50); getch(); //сдвиг окружности
CF1.move(221,100); getch(); //сдвиг залитой окружности
close_gs(); //закрытие графической системы
}
Можно создать массив указателей на объекты класса GO и заполнить его адресами графических объектов разного типа. Применение к этим указателям методов с одними и теми же названиями, приводит к вызовам методов тех классов, чей тип совпадает с типом адресуемого объекта. Это и есть демонстрация одного из важнейших принципов объектно-ориентированного подхода – полиморфизма:
#include "gs.h"
#include <conio.h>
void main()
{ open_gs();
point P1(21,10,2);
circ C1(21,50,20,4);
circf CF1(21,100,20,12);
GO *m[3]={&P1,&C1,&CF1}; //массив указателей
for(int i=0;i<3;i++) m[i]->show(); //отображение объектов
getch();
m[0]->move(121,10); getch(); //сдвиг точки
m[1]->move(121,50); getch(); //сдвиг окружности
m[2]->move(121,100); getch(); //сдвиг залитой окружности
close_gs();
}