- •6Vpj7-h3cxh-hbtpt-x4t74-3yvy7
- •Оглавление
- •Предисловие
- •Введение
- •1.1. Понятие класса и объекта. Инкапсуляция
- •1.2. Определение классов. Компоненты. Доступность
- •Class_key /*class_id*/ { /*members_list*/ };
- •Value_type class_id::function_id(parameters) {statements}
- •Static cPoint point3(50,120); // статический объект
- •Class_id(parameters) /*:initializer_list*/ {/*statements*/}
- •CPoint point1(100,70); // локальный объект
- •CString(const char *);
- •Delete[] __thematrix;
- •1.4. Обращение к компонентам объектов
- •1.5. Статические и нестатические компоненты классов
- •1.7. Указатель this
- •В опросы для самопроверки
- •2. Механизм наследования. Полиморфизм
- •2.1. Формы наследования. Базовые и производные классы
- •Class_key class_id: inheritance_specifier base_class_id {member_list};
- •2.3. Абстрактные классы
- •2.4. Множественное наследование и виртуальные классы
- •2.5. Преобразование динамических типов. Динамическая идентификация типов
- •Catch ( std::bad_cast & ) { // обработка исключения
- •Return 0;
- •Вопросы для самопроверки
- •3. Дружественные функции и классы
- •3.1. Дружественные функции
- •3.2. Дружественные классы
- •Вопросы для самопроверки
- •4. Механизм вложения
- •4.1. Вложенные классы
- •4.2. Локальные классы
- •Вопросы для самопроверки
- •5. Объектная модель и шаблоны
- •5.1. Определение, описание и инстанцирование шаблонов
- •::Function_id(function_parameter_list) { statements }
- •5.2. Параметры и аргументы шаблонов
- •Class identifier typename identifier
- •// Key, Data – параметры-типы (типы ключа и данных отображения)
- •// Container – контейнер, где содержится информация отображения class сMap {
- •Class MyTemplate
- •Int array[10]; struct Structure { int m; static int sm; } str;
- •5.3. Шаблоны компонентных функций
- •Value_type function_template_id(function_parameter_list) { statements }
- •::Function_template_id(function_parameter_list) { statements }
- •5.4. Специализация шаблонов
- •Вопросы для самопроверки
- •6. Перегрузка операций
- •Value_type operator @ (parameter_list);
- •Value_type operator @ (parameter_list) { statements }
- •Return fail();
- •6.3. Перегрузка бинарных операций
- •Value_type operator @ (parameter); // компонентная функция
- •Value_type operator @ (parameter, parameter); // глобальная функция friend value_type operator @ (parameter, parameter); // дружественная функция
- •Return *this;
- •Return *this;
- •/* Присваиваем собственные данные класса d */
- •6.4. Перегрузка операций управления памятью
- •Typedef void (*new_handler) ();
- •Extern new_handler set_new_handler( new_handler new_p );
- •Void operator delete(void * memory) {
- •... // Специальная обработка пользователя ::operator delete(memory); // освободить память
- •Вопросы для самопроверки
- •7. Механизм исключений
- •Throw expression
- •7.3. Специальные средства поддержки механизма исключений
- •Unexpected_function set_unexpected(unexpected_function func_name);
- •Typedef void (* unexpected_function) ();
- •Extern char * __throwExceptionName; extern char * __throwFileName; extern unsigned __throwLineNumber;
- •Вопросы для самопроверки
- •8. Подсчет ссылок
- •8.1. Назначение механизма подсчета ссылок
- •8.2. Контекстно-независимая модель счетчика ссылок
- •8.4. Внедрение подсчета ссылок в существующий класс
- •Вопросы для самопроверки
- •9. Стандартная библиотека шаблонов (stl)
- •9.1. Назначение и архитектура stl
- •9.2. Последовательные контейнеры
- •Class vector {
- •// Определение итераторов
- •Sort(first,last); // сортировка вектора в диапазоне итераторов
- •Ifstream ifile ("example.In"); ofstream ofile ("example.Out");
- •OutputIterator copy(
- •InputIterator first, InputIterator last, OutputIterator result );
- •// Заполнение списка
- •Operator- (int)
- •Operator- (random access iterator) operator[] (int)
- •InputIterator find(InputIterator first, InputIterator last, const t & value);
- •InputIterator find(InputIterator first, InputIterator last, const t & value)
- •Return first;
- •OutputIterator copy (InputIterator first, InputIterator last, OutputIterator result)
- •Return result;
- •OutputIterator transform (InputIterator first, InputIterator last, OutputIterator result, UnaryOperation op)
- •Return result;
- •Void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp)
- •__Quick_sort_loop(first, last, comp); __final_insertion_sort(first, last, comp);
- •9.5. Функторы
- •T operator()(const t & X) const { return -X; }
- •T accumulate(InputIterator first, InputIterator last, t init, Function f);
- •V.Push_back(2); V.Push_back(5);
- •9.7. Адаптеры
- •S1.Push(1); s1.Push(5);
- •// Записать в вектор числа 1 2 3 4
- •// Сортировать по неубыванию
- •// Записать в вектор числа 4 6 10 3 13 2
- •Вопросы для самопроверки
- •Заключение
- •Библиографический Список
- •6Vpj7-h3cxh-hbtpt-x4t74-3yvy7
/* Присваиваем собственные данные класса d */
...
}
Особенность приведенной реализации в том, что в ней путем приведения статического типа текущего объекта к B& «провоцируется» неявный вызов функции B::operator= вместо D::operator=. Особое внимание необходимо уделить использованию B&, а не B, так как во втором случае вызов будет происходить для временного объекта, создаваемого в результате преобразования типа.
Еще одной операцией, перегрузка которой имеет определенные особенности, является индексация (operator[]). Для перегрузки операции [] необходимо определить нестатическую компонентную функцию-операцию operator[] с единственным параметром целого типа. Тогда запись X[i] для объекта X некоторого класса C, содержащего operator[], будет транслироваться в формат X.operator[](i).
Главный вопрос при перегрузке operator[] связан с тем, что функций-операций должно быть две. Первая для неконстантного объекта, а вторая для константного. Функция для неконстантного объекта должна возвращать ссылку на тип элемента без модификаторов, что делает результат ее вызова леводопустимым выражением. Функция для константного объекта должна возвращать ссылку на тип элемента с квалификатором const (или даже просто значение элемента). В результате ее вызов будет праводопустимым выражением.
Ниже приведен пример определения класса, моделирующего вещественные массивы с контролем диапазона индексов; в этом классе определяется операция operator[]:
class CArray {
public:
double & operator[] (int i)
{ // для неконстантного объекта
if (i < 0 || i >= __MaxLen)
throw std::range_error(“invalid index”);
return __Array[i];
}
double operator[] (int i) const
{ // для константного объекта
if (i < 0 || i >= __MaxLen) return MAXDOUBLE;
return __Array[i];
}
...
private:
static const int __MaxLen;
double __Array[__MaxLen];
};
В примере функция operator[] для константного объекта при неправильном индексе просто возвращает значение «бесконечность» (MAXDOUBLE), а функция для константного объекта возбуждает исключение range_error. Это обусловлено тем, что запись по неверному индексу является более серьезной проблемой нежели чтение и продолжать программу при ее наличии вряд ли разумно.
Практически любой программист, даже начинающий, интенсивно работает с потоками, используя операции << и >> для обмена данными с потоками. Они дают удобный синтаксис, который весьма легко запоминается. Поэтому для вывода в поток и чтения из потока объектов пользовательских классов тоже разумно использовать эти операции.
Операции << и >> перегружаются глобальными (как правило, дружественными) функциями-операциями. Перегрузить их компонентными функциями класса можно, но это приведет к неестественному синтаксису, поскольку объект будет левым операндом, а поток правым:
class X {
public:
...
// плохой вариант перегрузки operator <<
ostream & operator << (ostream & os) {
os << __m1 << “ “ << __m2 << endl;
return os;
}
// плохой вариант перегрузки operator >>
istream & operator >> (istream & is) {
is >> __m1 >> __m2;
return is;
}
private:
int __m1;
double __m2;
};
// вызов функций operator << и operator >>
X x;
...
x.operator<<(cout); // функциональная форма вызова: нормально
x.operator>>(cin); // функциональная форма вызова: нормально
x << cout; // операционная форма: странный синтаксис
x >> cin; // операционная форма: странный синтаксис
Дружественность функции operator << (или operator >>) необходима из-за того, что в ходе ввода-вывода выполняется работа с данными объекта, а они, как правило, входят в закрытую (защищенную) часть класса. Если для чтения данных класса есть отдельные функции, то operator << (operator >>) можно не делать дружественной функцией.
Ниже дан пример корректного определения функций operator << и operator >>.
Пример
class X {
public:
...
// верный вариант перегрузки operator <<
friend ostream & operator << (ostream &, const X &);
// верный вариант перегрузки operator >>
friend istream & operator >> (istream &, X &);
private:
int __m1;
double __m2;
};
// определение функций operator << и operator >>
ostream & operator << (ostream & os, const X & x) {
os << x.__m1 << “ “ << x.__m2 << endl;
return os;
}
istream & operator >> (istream & is, X & x) {
is >> x.__m1 >> x.__m2;
return is;
}
// вызов функций operator << и operator >>
X x;
...
operator<<(cout,x); // функциональная форма вызова: нормально
operator>>(cin,x); // функциональная форма вызова: нормально
cout << x; // операционная форма: удобный синтаксис
cin >> x; // операционная форма: удобный синтаксис
Еще один аспект корректности определения функций operator << и operator >> связан с правильной спецификацией возвращаемого значения. Выше в примерах его тип определялся как неконстантная ссылка на поток. Это разумный вариант, поскольку благодаря ему становятся возможными серии операций ввода-вывода в одном операторе:
X x,y,z;
...
cin >> x >> y >> z;
cout << x << y << z;
...
Если использовать иной вариант спецификации, то такие цепочки станут невозможны. Например, ошибочно определять функцию operator << с заголовком вида:
void operator << (ostream & os, const X & x);
То же самое справедливо и для перегрузки операции >>.
И последний момент, на который надо обратить внимание: потоки os и is передаются в operator << (>>) как неконстантная ссылка, поскольку имеют значительный размер и, кроме того, изменяют свое состояние при выполнении операций ввода-вывода.
В число операций, для которых разрешается перегрузка, входит операция функционального вызова operator(). Использование этой операции создает удобный синтаксис, при котором имя объекта класса можно рассматривать как имя функции. Класс, который содержит operator(), часто называют классом функциональных объектов, или классом функторов (а сами объекты именуют функторами)34.
Операцию operator() можно перегрузить нестатической компонентной функцией-операцией. Эта функция может иметь список из произвольного числа параметров (параметры могут вообще отсутствовать)35. Вызов ее в операционной и функциональной формах будет иметь вид соответственно:
object_id(arguments);
object_id.operator()(arguments);
где object_id – имя объекта, класс которого определяет функцию operator()36; arguments – список аргументов.
Ниже дан пример определения класса с функцией operator(), а также его использования.
Пример
class Function {
public:
...
double operator()(double,double); // прототип операции
private:
...
};
// внешнее определение операции
double Function::operator()(double a, double b) {
...
}
// использование класса Function
Function f;
double z = f(2.5,5.6); // операционная форма вызова
double w = f.operator()(2.5,5.6); // функциональная форма вызова
Функция operator() может иметь параметры со значениями по умолчанию. Также допускается наличие в одном и том же классе нескольких перегруженных вариантов функции operator(). Их выбор осуществляется по порядку и типам параметров, как и в случае обычной перегрузки функций.
