- •Кетков ю.Л.
- •Раздел 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.1.2. Вызов конструкторов и деструкторов при наследовании
Последовательность вызова конструкторов и деструкторов легче проследить на следующем примере. В базовом классе B содержится единственный закрытый член данных x, предусмотрены три конструктора (по умолчанию, инициализации и копирования), функция опроса значения закрытого поля и деструктор. Каждый из них выводит свое условное обозначение при вызове. В производном классе D, который наследует поле x в режиме private, содержится и собственное закрытое поле y. В его составе такие же три конструктора, функция опроса значения закрытого поля и деструктор.
Головная программа сначала создает четыре объекта w1, w2, w3 и w4 типа B, а затем четыре объекта q1, q2, q3 и q4 типа D. После создания каждого объекта фиксируется содержимое соответствующих полей и цепочка вызываемых конструкторов. Перед окончанием программы фиксируется цепочка вызовов деструкторов.
#include <iostream.h>
#include <conio.h>
class B {
int x;
public:
B(){x=0; cout<<"Def_B "<<endl;}
B(int n){x=n; cout<<"Init_B "<<endl;}
B(const B &y){x=y.x; cout<<"Copy_B "<<endl;}
int get_x(){return x;}
~B(){cout<<"Destr_B"<<endl;}
};
class D : public B {
int y;
public:
D(){y=0; cout<<"Def_D "<<endl;}
D(int n){y=n; cout<<"Init_D "<<endl;}
D(const D &z){y=z.y; cout<<"Copy_D "<<endl;}
int get_y(){return y;}
~D(){cout<<"Destr_D"<<endl;}
};
void main()
{ B w1;
cout<<"w1.x="<<w1.get_x()<<endl;
B w2(2);
cout<<"w2.x="<<w2.get_x()<<endl;
B w3(w2);
cout<<"w3.x="<<w3.get_x()<<endl;
B w4=w1;
cout<<"w4.x="<<w4.get_x()<<endl;
D q1;
cout<<"q1.x="<<q1.get_x()<<' '<<"q1.y="<<q1.get_y()<<endl;
D q2(2);
cout<<"q2.x="<<q2.get_x()<<' '<<"q2.y="<<q2.get_y()<<endl;
D q3(q2);
cout<<"q3.x="<<q3.get_x()<<' '<<"q3.y="<<q3.get_y()<<endl;
D q4=q1;
cout<<"q4.x="<<q4.get_x()<<' '<<"q4.y="<<q4.get_y()<<endl;
}
//=== Результаты работы ===
Def_B //конструктор B по умолчанию для создания w1.x
w1.x=0 //значение созданного объекта
Init_B //конструктор B инициализации для создания w2.x
w2.x=2 //значение созданного объекта
Copy_B //конструктор B копирования для создания w3.x
w3.x=2 //значение созданного объекта
Copy_B //конструктор B копирования для создания w4.x
w4.x=0 //значение созданного объекта
Def_B //неявный вызов конструктора B для создания q1.x
Def_D //конструктор D по умолчанию для создания q1.y
q1.x=0 q1.y=0 //значения созданных объектов
Def_B //неявный вызов конструктора B для создания q2.x
Init_D //конструктор D инициализации для создания q2.y
q2.x=0 q2.y=2 //значения созданных объектов
Def_B //неявный вызов конструктора B для создания q3.x
Copy_D //конструктор D копирования для создания w3.y
q3.x=0 q3.y=2 //значения созданных объектов
Def_B //неявный вызов конструктора B для создания q4.x
Copy_D //конструктор D копирования для создания w4.y
q4.x=0 q4.y=0 //значения созданных объектов
Destr_D //деструктор D для уничтожения w4.y
Destr_B //деструктор B для уничтожения w4.x
Destr_D //деструктор D для уничтожения w3.y
Destr_B //деструктор B для уничтожения w3.x
Destr_D //деструктор D для уничтожения w2.y
Destr_B //деструктор B для уничтожения w2.x
Destr_D //деструктор D для уничтожения w1.y
Destr_B //деструктор B для уничтожения w1.x
Destr_B //деструктор B для уничтожения q4.x
Destr_B //деструктор B для уничтожения q3.x
Destr_B //деструктор B для уничтожения q2.x
Destr_B //деструктор B для уничтожения q1.x
Обратите внимание на то, что в этом примере создание объектов производного класса начинается с автоматического вызова конструктора базового класса по умолчанию. Кроме того, объекты уничтожаются в порядке, обратном последовательности их создания – самый первый объект разрушается последним.
Однако возможна ситуация, когда ни программист, ни система не включили в базовый класс конструктор по умолчанию. Это происходит в тех случаях, когда программист написал только конструкторы с параметрами. В такой ситуации конструкторы производного класса должны сами позаботиться об инициализации объектов родительского класса. Сделать это можно разными способами – явно вызвать конструктор базового класса либо в своем списке инициализации, либо в теле конструктора. Для защищенных (protected) полей базового класса можно воспользоваться указателем this. В приводимом ниже примере демонстрируются эти возможности. В качестве базового класса выступает класс Point2D, моделирующий точку на плоскости:
class Point2D {
int x,y; //закрытые данные класса Point2D
public:
Point2D(int xx,int yy):x(xx),y(yy){} //конструктор инициализации
Point2D(const Point2D &P):x(P.x),y(P.y){} //конструктор копирования
int get_x(){return x;}
int get_y(){return y;}
};
Порожденный класс Point3D моделирует точку в трехмерном пространстве:
class Point3D: public Point2D {
int z; //новая координата в классе Point3D
public:
Point3D(int xx,int yy,int zz):Point2D(xx,yy),z(zz){}
int get_z(){return z;} //новый метод в классе Point3D
};
А теперь протестируем оба класса на следующей программе:
#include <iostream.h>
#include <conio.h>
void main()
{ Point2D P2(1,2);
Point3D P3(3,4,5);
cout<<"P3.x="<<P3.get_x()<<" P3.y="<<P3.get_y()<<" P3.z=" <<P3.get_z()<<endl;
cout<<"P2.x="<<P2.get_x()<<" P2.y="<<P2.get_y()<<endl;
P2=P3;
cout<<"P2.x="<<P2.get_x()<<" P2.y="<<P2.get_y()<<endl;
getch();
}
//=== Результат работы ===
P3.x=3 P3.y=4 P3.z=5
P2.x=1 P2.y=2
P2.x=3 P2.y=4
Производному классу по наследству достались приватные данные – координаты (x,y) родительского объекта и общедоступные методы доступа к этим координатам. Поэтому в головной программе мы можем пользоваться этими методами как по отношению к объектам типа Point2D, так и по отношению к объектам типа Point3D. Немного странным кажется оператор присваивания двухмерному объекту P2 значения трехмерного объекта P3. Но происходит вполне естественная операция – те поля, которые являются общими у этих двух объектов, переносятся, а "лишнее" поле P3.z отсекается. Обратная операция P3=P2 была бы ошибочной, т.к. компилятор не "знает", чем следует заполнить поле P3.z.
Если бы поля (x,y) в базовом классе были объявлены как защищенные (protected), то их инициализацию в конструкторе производного класса можно было бы выполнить и так:
Point3D(int xx,int yy,int zz):z(zz)
{ this->x=xx; this->y=yy; }