- •Глава 6 посвящена понятию производных классов, которое позволяет строить
- •Раздел 3.4 главы 2. Для обозначения справочного руководства применяется
- •1991 Г.Г. (такие как множественное наследование, статические функции-члены
- •1.1 Введение
- •1.2 Парадигмы программирования
- •1.2.1 Процедурное программирование
- •1.2.5 Объектно-ориентированное программирование
- •1.5 Поддержка объектно-ориентированного программирования
- •1.5.1 Механизм вызова
- •1.5.2 Проверка типа
- •1.5.3 Множественное наследование
- •1.6 Пределы совершенства
- •2.2 Имена
- •2.3.2 Неявное преобразование типа
- •2.4 Литералы
- •2.4.4 Строки
- •2.6. Экономия памяти
- •2.6.1 Поля
- •3.1.1 Анализатор
- •3.1.2 Функция ввода
- •3.2 Сводка операций
- •3.2.3 Инкремент и декремент
- •3.2.5 Преобразование типа
- •3.2.6 Свободная память
- •3.3.2 Оператор goto
- •4.1 Введение
- •4.3.1 Единственный заголовочный файл
- •4.3.2 Множественные заголовочные файлы
- •4.4 Связывание с программами на других языках
- •4.6.3 Передача параметров
- •5.1 Введение и краткий обзор
- •5.3.1 Альтернативные реализации
- •5.3.2 Законченный пример класса
- •Vector и matrix, мы могли бы обойтись без контроля индекса при
- •5.4.5 Указатели на члены
- •5.4.6 Структуры и объединения
- •5.5.3 Свободная память
- •5.5.5 Массивы объектов класса
- •6.1 Введение и краткий обзор
- •6.2.3 Иерархия классов
- •6.2.4 Поля типа
- •6.4.1 Монитор экрана
- •6.5 Множественное наследование
- •7.1 Введение
- •7.3 Пользовательские операции преобразования типа
- •7.3.2 Операции преобразования
- •7.3.3 Неоднозначности
- •7.5 Большие объекты
- •Void f2(t a) // вариант с контролем
- •Void f3(t a) // вариант с контролем
- •Inv() обращает саму матрицу m, а не возвращает новую, обратную m,
- •7.13 Предостережения
- •8.1 Введение
- •8.4.4 Неявная передача операций
- •8.4.5 Введение операций с помощью параметров шаблонного класса
- •8.7.1 Задание реализации с помощью параметров шаблона
- •9.1 Обработка ошибок
- •9.1.2 Другие точки зрения на особые ситуации
- •9.3.2 Производные особые ситуации
- •9.4.2 Предостережения
- •9.4.3 Исчерпание ресурса
- •9.4.4 Особые ситуации и конструкторы
- •9.5 Особые ситуации могут не быть ошибками
- •10.1 Введение
- •10.2 Вывод
- •10.2.1 Вывод встроенных типов
- •10.4.1.2 Поля вывода
- •10.4.1.4 Вывод целых
- •Istream - шаблон типа smanip, а smanip - двойник для ioss.
- •10.5.1 Закрытие потоков
- •10.5.2 Строковые потоки
- •X Целый параметр выдается в шестнадцатеричной записи;
- •11.1 Введение
- •11.2 Цели и средства
- •11.3 Процесс развития
- •11.3.1 Цикл развития
- •11.3.2 Цели проектирования
- •11.3.3 Шаги проектирования
- •11.3.3.1 Шаг 1: определение классов
- •11.3.3.2 Шаг 2: определение набора операций
- •11.3.3.3 Шаг 3: указание зависимостей
- •11.3.3.4 Шаг 4: определение интерфейсов
- •11.3.3.5 Перестройка иерархии классов
- •11.3.3.6 Использование моделей
- •11.3.4 Эксперимент и анализ
- •11.3.5 Тестирование
- •11.3.6 Сопровождение
- •11.3.7 Эффективность
- •11.4 Управление проектом
- •11.4.1 Повторное использование
- •11.4.2 Размер
- •11.4.3 Человеческий фактор
- •11.5 Свод правил
- •11.6 Список литературы с комментариями
- •12.1 Проектирование и язык программирования.
- •12.1.1 Игнорирование классов
- •12.1.2 Игнорирование наследования
- •12.1.3 Игнорирование статического контроля типов
- •12.1.4 Гибридный проект
- •12.2 Классы
- •12.2.1 Что представляют классы?
- •12.2.2 Иерархии классов
- •12.2.3 Зависимости в рамках иерархии классов.
- •Vertical_scrollbar или с помощью одного типа scrollbar, который
- •12.2.6 Отношения использования
- •12.2.7 Отношения внутри класса
- •12.3 Компоненты
- •12.4 Интерфейсы и реализации
- •12.5 Свод правил
- •13.1 Введение
- •13.2 Конкретные типы
- •13.4 Узловые классы
- •1, 2, 6 И 7. Класс, который не удовлетворяет условию 6, походит
- •13.5.1 Информация о типе
- •13.6 Обширный интерфейс
- •13.7 Каркас области приложения
- •13.8 Интерфейсные классы
- •13.10 Управление памятью
7.1 Введение
Обычно в программах используются объекты, являющиеся конкретным
представлением абстрактных понятий. Например, в С++ тип данных int
вместе с операциями +, -, *, / и т.д. реализует (хотя и ограниченно)
математическое понятие целого. Обычно с понятием связывается набор
действий, которые реализуются в языке в виде основных операций над
объектами, задаваемых в сжатом, удобном и привычном виде.
К сожалению, в языках программирования непосредственно представляется
только малое число понятий. Так, понятия комплексных чисел, алгебры
матриц, логических сигналов и строк в С++ не имеют непосредственного
выражения. Возможность задать представление сложных объектов вместе
с набором операций, выполняемых над такими объектами,
реализуют в С++ классы. Позволяя программисту определять операции
над объектами классов, мы получаем более удобную и традиционную
систему обозначений для работы с этими объектами по сравнению с той,
в которой все операции задаются как обычные функции. Приведем пример:
class complex {
double re, im;
public:
complex(double r, double i) { re=r; im=i; }
friend complex operator+(complex, complex);
friend complex operator*(complex, complex);
};
Здесь приведена простая реализация понятия комплексного числа, когда
оно представлено парой чисел с плавающей точкой двойной точности,
с которыми можно оперировать только с помощью операций + и *.
Интерпретацию этих операций задает программист в определениях функций
с именами operator+ и operator*. Так, если b и c имеют тип complex,
то b+c означает (по определению) operator+(b,c). Теперь можно
приблизиться к привычной записи комплексных выражений:
void f()
{
complex a = complex(1,3.1);
complex b = complex(1.2,2);
complex c = b;
a = b+c;
b = b+c*a;
c = a*b+complex(1,2);
}
Сохраняются обычные приоритеты операций, поэтому второе выражение
выполняется как b=b+(c*a), а не как b=(b+c)*a.
7.2 Операторные функции
Можно описать функции, определяющие интерпретацию следующих операций:
+ - * / % ^ & | ~ !
= < > += -= *= /= %= ^= &=
|= << >> >>= <<= == != <= >= &&
|| ++ -- ->* , -> [] () new delete
Последние пять операций означают: косвенное обращение ($$7.9),
индексацию ($$7.7), вызов функции ($$7.8), размещение в свободной
памяти и освобождение ($$3.2.6). Нельзя изменить приоритеты этих
операций, равно как и синтаксические правила для выражений. Так,
нельзя определить унарную операцию % , также как и бинарную операцию
!. Нельзя ввести новые лексемы для обозначения операций, но если
набор операций вас не устраивает, можно воспользоваться привычным
обозначением вызова функции. Поэтому используйте pow(), а не ** .
Эти ограничения можно счесть драконовскими, но более свободные
правила легко приводят к неоднозначности. Допустим, мы определим
операцию ** как возведение в степень, что на первый взгляд кажется
очевидной и простой задачей. Но если как следует подумать, то
возникают вопросы: должны ли операции ** выполняться слева направо
(как в Фортране) или справа налево (как в Алголе)? Как
интерпретировать выражение a**p как a*(*p) или как (a)**(p)?
Именем операторной функции является служебное слово operator, за
которым идет сама операция, например, operator<<. Операторная
функция описывается и вызывается как обычная функция. Использование
символа операции является просто краткой формой записи вызова
операторной функции:
void f(complex a, complex b)
{
complex c = a + b; // краткая форма
complex d = operator+(a,b); // явный вызов
}
С учетом приведенного описания типа complex инициализаторы в этом
примере являются эквивалентными.
7.2.1 Бинарные и унарные операции
Бинарную операцию можно определить как функцию-член с одним
параметром, или как глобальную функцию с двумя параметрами. Значит,
для любой бинарной операции @ выражение aa @ bb интерпретируется
либо как aa.operator(bb), либо как operator@(aa,bb). Если определены обе
функции, то выбор интерпретации происходит по правилам сопоставления
параметров ($$R.13.2). Префиксная или постфиксная унарная операция
может определяться как функция-член без параметров, или как глобальная
функция с одними параметром. Для любой префиксной унарной операции
@ выражение @aa интерпретируется либо как aa.operator@(), либо как
operator@(aa). Если определены обе функции, то выбор интерпретации
происходит по правилам сопоставления параметров ($$R.13.2). Для
любой постфиксной унарной операции @ выражение @aa интерпретируется
либо как aa.operator@(int), либо как operator@(aa,int). Подробно
это объясняется в $$7.10. Если определены обе функции, то выбор
интерпретации происходит по правилам сопоставления параметров
($$13.2). Операцию можно определить только в соответствии с
синтаксическими правилами, имеющимися для нее в грамматике С++.
В частности, нельзя определить % как унарную операцию, а + как
тернарную. Проиллюстрируем сказанное примерами:
class X {
// члены (неявно используется указатель `this'):
X* operator&(); // префиксная унарная операция &
// (взятие адреса)
X operator&(X); // бинарная операция & (И поразрядное)
X operator++(int); // постфиксный инкремент
X operator&(X,X); // ошибка: & не может быть тернарной
X operator/(); // ошибка: / не может быть унарной
};
// глобальные функции (обычно друзья)
X operator-(X); // префиксный унарный минус
X operator-(X,X); // бинарный минус
X operator--(X&,int); // постфиксный инкремент
X operator-(); // ошибка: нет операнда
X operator-(X,X,X); // ошибка: тернарная операция
X operator%(X); // ошибка: унарная операция %
Операция [] описывается в $$7.7, операция () в $$7.8, операция ->
в $$7.9, а операции ++ и -- в $$7.10.
7.2.2 Предопределенные свойства операций
Используется только несколько предположений о свойствах пользовательских
операций. В частности, operator=, operator[], operator() и
operator-> должны быть нестатическими функциями-членами. Этим
обеспечивается то, что первый операнд этих операций является адресом.
Для некоторых встроенных операций их интерпретация определяется
как комбинация других операций, выполняемых над теми же операндами.
Так, если a типа int, то ++a означает a+=1, что в свою очередь
означает a=a+1. Такие соотношения не сохраняются для пользовательских
операций, если только пользователь специально не определил их с такой
целью. Так, определение operator+=() для типа complex нельзя вывести
из определений complex::operator+() и complex operator=().
По исторической случайности оказалось, что операции = (присваивание),
&(взятие адреса) и , (операция запятая) обладают предопределенными
свойствами для объектов классов. Но можно закрыть от произвольного
пользователя эти свойства, если описать эти операции как частные:
class X {
// ...
private:
void operator=(const X&);
void operator&();
void operator,(const X&);
// ...
};
void f(X a, X b)
{
a= b; // ошибка: операция = частная
&a; // ошибка: операция & частная
a,b // ошибка: операция , частная
}
С другой стороны, можно наоборот придать с помощью соответствующих
определений этим операциям иное значение.
7.2.3 Операторные функции и пользовательские типы
Операторная функция должна быть либо членом, либо иметь по крайней
мере один параметр, являющийся объектом класса (для функций,
переопределяющих операции new и delete, это не обязательно). Это
правило гарантирует, что пользователь не сумеет изменить
интерпретацию выражений, не содержащих объектов пользовательского
типа. В частности, нельзя определить операторную функцию, работающую
только с указателями. Этим гарантируется, что в С++ возможны
расширения, но не мутации (не считая операций =, &, и , для объектов
класса).
Операторная функция, имеющая первым параметр основного типа,
не может быть функцией-членом. Так, если мы прибавляем комплексную
переменную aa к целому 2, то при подходящем описании функции-члена
aa+2 можно интерпретировать как aa.operator+(2), но 2+aa так
интерпретировать нельзя, поскольку не существует класса int, для
которого + определяется как 2.operator+(aa). Даже если бы это было
возможно, для интерпретации aa+2 и 2+aa пришлось иметь дело с двумя
разными функциями-членами. Этот пример тривиально записывается
с помощью функций, не являющихся членами.
Каждое выражение проверяется для выявления неоднозначностей.
Если пользовательские операции задают возможную интерпретацию
выражения, оно проверяется в соответствии с правилами $$R.13.2.
