- •Кетков ю.Л.
- •Раздел 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. Исключения
15.6. Переопределение операций (резюме)
Создавая новый тип данных, мы были вынуждены объяснить компилятору, как надлежит выполнять обычные операции (сложения, вычитания, сравнения, ввод/вывод и т.п.) с объектами нового типа. По сути дела, операции – это простейшие функции, на вход которых поступает один или два операнда, над ними выполняется обусловленное действие и формируется возвращаемое значение. В этом смысле переопределение операций, практически, ничем не отличается от переопределения функций. В самом общем виде операция переопределяется следующим образом:
тип_результата имя_класса::operator(список_аргументов)
{ //процедура выполнения новой операции }
Здесь – обозначение знака операции.
Единственное отличие от переопределения функции заключается в специфическом имени, заменяющем имя функции.
В связи с тем, что должна быть сохранена логика обработки компилятором арифметических и логических формул, процедур ввода/вывода, операторов присваивания, инкрементирования и декрементирования, на переопределение операций накладываются три важных ограничения:
приоритет операций, описанный в стандарте языка C++, менять нельзя;
количество операндов, которое изначально было объявлено в языке, для каждой операции должно быть сохранено;
нельзя переопределять операции '.' (разделитель в составных именах), '.*' (обращение к полю объекта через указатель), '::' (спецификатор принадлежности), '?:' (условное выражение), '#' (директива препроцессора), '##' (операция конкатенации в препроцессоре).
При написании процедуры переопределения двухместной операции a1a2 надо помнить, что аргумент a1, расположенный левее знака операции, передается в функцию-член класса двумя способами. Во-первых, как поля объекта, объявленные в качестве членов-данных класса. Во-вторых, как скрытый указатель this. Поэтому первый операнд операции как аргумент в функции переопределения не указывается.
Еще одно ограничение на функцию переопределения операции заключается в том, что среди ее параметров нельзя пользоваться значениями по умолчанию.
Рассмотрим варианты переопределения двухместных операций на примере класса Tpoint, представляющего точку с парой целочисленных координат:
class Tpoint {
int x,y;
public
Tpoint(){x=0;y=0;} //конструктор по умолчанию
Tpoint(int xx,int yy){x=xx;y=yy;} //конструктор инициализации
void GeTpoint(int &xx,int &yy){xx=x; yy=y;} //опрос координат
Tpoint operator+(Tpoint P2);
};
Операция сложения, объявленная в приведенном выше примере, принимает второе слагаемое как значение. Поэтому описание новой процедуры может выглядеть следующим образом:
Tpoint Tpoint::operator+(Tpoint P2)
{ Tpoint q;
q.x=x+P2.x; q.y=y+P2.y;
return q;
}
Более экономный вариант функции сложения заключается в использовании ссылки на объект P2:
Tpoint Tpoint::operator+(Tpoint &P2)
{ Tpoint q;
q.x=x+P2.x; q.y=y+P2.y;
return q;
}
Во-первых, ссылка на объект P2 – это 4 байта, тогда как передача значения P2 потребовала бы передачи двух четырехбайтовых координат. Во-вторых, для значения P2, являющегося формальным параметром первого варианта функции, пришлось бы выделять временную память (как и для локальной переменной q), а при выходе из функции эту память освобождать.
Операция присваивания над объектами типа Tpoint a1=a2 тоже является двухместной, но ее переопределение использует указатель this, чтобы возвратить значение:
Tpoint Tpoint::operator=(Tpoint &P2)
{ x=P2.x; y=P2.y; return *this; }
В переопределении логических операций и операций отношения (хотя для точек большинство из этих действий лишено смысла) имеется особенность – они должны возвращать целочисленное значение, соответствующее истине (не нуль) или лжи (нуль):
int Tpoint::operator==( Tpoint &P2)
{ return (x==P2.x)&&(y==P2.y); }
Специфика переопределения унарных операций (над одним операндом) заключается в том, что соответствующая функция не имеет формальных параметров, хотя операнд, расположенный левее знака операции она "знает". Например, операция двухместного вычитания и операция смены знака у точки могут быть переопределены следующим образом:
Tpoint Tpoint::operator-(Tpoint &P2)
{ Tpoint q;
q.x=x-P2.x; q.y=y-P2.y; return q;
}
Tpoint Tpoint::operator-()
{ x=-x; y=-y; return *this; }
Мы уже упоминали, что с операциями левого и правого декремента ситуация несколько сложнее. Операция ++P1 мало чем отличается от унарной смены знака -P1, и ее переопределение вопросов не вызывает:
Tpoint Tpoint::operator++()
{ x++; y++; return *this; }
А вот для операции P1++ в реализации C++ пришлось придумывать специальный выход – было решено, что если знак инкремента (или декремента) следует за операндом, то после этого знака якобы появляется фиктивный нулевой операнд. Поэтому соответствующее переопределение операции P1++ "использует" фиктивный формальный параметр:
Tpoint Tpoint::operator++(int P)
{ Tpoint q=*this;
x++; y++; return q; }
Иногда для переопределения операций прибегают к услугам дружественных функций. Хотя дружественные функции и имеют доступ к приватным данным класса, но указатель this они не получают. Поэтому, например, с их помощью нельзя переопределить операцию '='. А другие унарные или бинарные операции такому переопределению поддаются легко, надо только передавать в дружественные функции на один параметр больше:
class Tpoint {
int x,y;
public
Tpoint(){x=0;y=0;} //конструктор по умолчанию
Tpoint(int xx,int yy){x=xx;y=yy;} //конструктор инициализации
void GeTpoint(int &xx,int &yy){xx=x; yy=y;} //опрос координат
friend Tpoint operator+(Tpoint P1,Tpoint P2);
};
Tpoint Tpoint::operator+(Tpoint P1,Tpoint P2)
{ Tpoint q;
q.x=P1.x+P2.x; q.y=P1.y+P2.y; return q;
}
О специфике переопределения операций потокового ввода и вывода мы уже упоминали при проектировании класса Rational. Продемонстрируем это еще раз на примере ввода и вывода данных типа "точка". Предположим, что нас устраивает следующий формат представления "точек" на вводе и выводе – (x,y), где x и y представляют целочисленные значения соответствующих координат. Напоминаем, что среди аргументов функций должна быть ссылка на входной (istream &) или выходной (ostream &) поток. Ввод или вывод должен быть организован по указанной ссылке и в качестве возвращаемого результата должна быть указана эта ссылка. Поэтому тип возвращаемого значения тоже должен быть ссылкой на входной или выходной поток:
#include <iostream.h>
#include <conio.h>
class Tpoint {
int x,y;
public
Tpoint(){x=0;y=0;} //конструктор по умолчанию
Tpoint(int xx,int yy){x=xx;y=yy;} //конструктор инициализации
void GetPoint(int &xx,int &yy){xx=x; yy=y;} //опрос координат
friend istream& operator>>(istream& t,Tpoint &P);
friend ostream& operator<<(ostream& t,Tpoint &P);
};
//ввод в формате (x,y) (дружественная функция)
istream& operator>>(istream& t,Tpoint &P)
{ char c; //для чтения разделителей /
t >> c >> P.x >> c >> P.y >> c;
return t; }
//-------------------------------------------------------
// вывод в формате (x,y) (дружественная функция)
ostream& operator<<(ostream& t,Tpoint &P)
{ t<<'('<<P.x<<','<<P.y<<')'; return t; }
void main()
{ Tpoint P(10,20);
cout<<P<<endl;
cin>>P;
cout<<P<<endl;
getch();
}
//=== Результат работы
(10,20)
(30,50) <Enter> //введена точка с новыми координатами
(30,50)