Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

hkjCJgcQqF

.pdf
Скачиваний:
2
Добавлен:
15.04.2023
Размер:
810.94 Кб
Скачать

также возбудить исключение, показывающее, что значение elem меньше 0 или больше длины C-строки, на которую ссылается _string.

#include <cassert>

inine char& MyString::operator[]( int elem ) const { assert( elem >= 0 && elem < _size );

return _string [ elem ]; }

В следующем фрагменте нулевому элементу массива color присваивается символ 'V'.

MyString color( "violet" ); color[ 0 ] = 'V';

Функцию operator()[] можно определить таким образом, чтобы оператор “[]” был допустимым и в левой, и в правой части оператора присваивания. Для этого нужно, чтобы функция operator () [ ] возвращала

ссылку. Пример:

 

#include <cstdlib>

 

#include <iostream>

 

using namespace std;

 

class MyClass

 

{

 

private:

 

int ar[3];

 

public:

 

MyClass( int a, int b,

int c)

{

 

ar[0] = a; ar[1 ]= b;

ar[2] = c; }

int &operator [ ] ( int i) { return ar [i ]; } };

int main ( )

{

MyClass myArr ( 5, 7, 4 ); cout << myArr [1] <<endl ; myArr [ 1] = 8 ;

cout << myArr [1] <<endl ; system("PAUSE"); return EXIT_SUCCESS;

}

Оператор вызова функции

Оператор вызова функции может быть перегружен для объектов типа класса. Если определен класс, представляющий некоторую операцию, то для ее вызова перегружается соответствующий оператор. Например, для взятия абсолютного значения числа типа int можно определить класс absInt:

class absInt {

73

public:

int operator()( int val ) {

int result = val < 0 ? -val : val; return result; }

};

Перегруженный оператор operator() должен быть объявлен как функция-член с произвольным числом параметров. Параметры и возвращаемое значение могут иметь любые типы, допустимые для функций. operator() вызывается путем применения списка аргументов к объекту того класса, в котором он определен. В следующем примере обобщенный алгоритм transform() вызывается для применения определенной в absInt операции к каждому элементу вектора ivec, т.е. для замены элемента его абсолютным значением.

#include <vector> #include <algoritm> using namespase std; int main() {

int ia[] = { -0, 1, -1, -2, 3, 5, -5, 8 }; vector< int > ivec( ia, ia+8 );

//заменить каждый элемент его абсолютным значением transform( ivec.begin(), ivec.end(), ivec.begin(), absInt() );

//...

}

Первый и второй аргументы transform() ограничивают диапазон элементов, к которым применяется операция absInt. Третий указывает на начало вектора, где будет сохранен результат применения операции.

Четвертый аргумент– это временный объект класса absInt, создаваемый с помощью конструктора по умолчанию.

Оператор “стрелка”

Оператор ссылки на член объекта при перегрузке считается унарным. Его общий вид:

объект -> элемент Оператор “стрелка”, разрешающий доступ к членам, может

перегружаться для объектов класса. Он должен быть определен как функция-член и обеспечивать семантику указателя. Чаще всего этот оператор используется в классах, которые предоставляют “интеллектуальный указатель” (smart pointer), ведущий себя аналогично встроенным, но поддерживают и некоторую дополнительную функциональность.

Допустим, мы хотим определить тип класса для представления указателя на объект MyClass :

class MyClassPtr { // ...

74

private:

MyClass *ptr; };

Определение MyClassPtr должно быть таким, чтобы объект этого класса гарантировано указывал на объект MyClass. В отличие от встроенного указателя, он не может быть нулевым и тогда приложение сможет пользоваться объектами типа MyClassPTR. Для всего выше сказанного нужно определить класс MyClassPTR с конструктором, но без конструктора по умолчанию.

class MyClassPtr { public:

MyClassPtr( const MyClass &mycl ) : ptr( & mycl ) { }

// ...};

 

В любом определении объекта класса

MyClassPtr должен

присутствовать инициализатор – объект класса MyClass, на который будет ссылаться объект MyClassPtr:

MyClassPtr myptr; // ошибка: у класса MyClassPtr нет конструктора по умолчанию

MyClass myClass( 57, 41 );

MyClassPtr myptr( myClass ); // правильно

Чтобы класс MyClassPtr вел себя как встроенный указатель, необходимо определить некоторые перегруженные операторы – разыменования (*) и “стрелку” для доступа к членам:

// перегруженные операторы для поддержки поведения указателя class MyClassPtr {

public:

MyClass & operator*( ) { return *ptr; } MyClass * operator->( ) { return ptr; } // ...

}; Оператор доступа к членам унарный, поэтому параметры ему не

передаются. При использовании в составе выражения его результат зависит только от типа левого операнда. Например, в инструкции:

obj->set_val();

исследуется тип obj. Если это указатель на некоторый тип класса, то применяется семантика встроенного оператора доступа к члену. Если же это объект или ссылка на объект, то проверяется, есть ли в этом классе перегруженный оператор доступа. Когда перегруженный оператор “стрелка” определен, он вызывается для объекта obj, иначе инструкция неверна, поскольку для обращения к членам самого объекта (в том числе по ссылке) следует использовать оператор “точка”.

Перегруженный оператор “стрелка” должен возвращать либо указатель на тип класса, либо объект класса, в котором он определен. Если возвращается указатель, то к нему применяется семантика встроенного

75

оператора “стрелка”. В противном случае процесс продолжается рекурсивно, пока не будет получен указатель или определена ошибка.

Создание префиксной и постфиксной форм операторов инкрементации и декрементации

Стандарт языка С++ позволяет явно создавать отдельные версии префиксного и постфиксного операторов инкрементации и декрементации. Общая форма префиксной и постфиксной операторных функций operator++( ) и operator - - ( )

//префиксный оператор инкрементации тип operator++( ) {

//тело префиксного оператора

}

//постфиксный оператор инкрементации тип operator++( int i ) {

//тело постфиксного оператора

}

//префиксный оператор декрементации тип operator --( ) {

//тело префиксного оператора

}

// постфиксный оператор декрементации

тип operator --( int i ) { // тело постфиксного оператора

}

Перегрузка операторов new и delete

Перегружать операторы new и delete приходится, если возникает необходимость создать особый механизм распределения памяти. Общая форма функций, перегружающих операторы new и delete :

//Выделение памяти для объекта void * operator new (size_t size )

{

/* Выделяется память. В случае неудачи генерируется исключительная ситуация bad_alloc.

Конструктор вызывается автоматически. */ return pointer_to_memory;

}

//Удаление объекта

void operator delete (void * p )

{

/* Освобождается память, на которую ссылается указатель p . Деструктор вызывается автоматически. */

}

76

Размер типа size_t соответствует единице выделяемой памяти. Параметр size задает количество байтов, необходимых для хранения объекта, размещаемого в памяти. Перегруженная функция new должна возвращать указатель на выделенную область памяти или генерировать исключительную ситуацию bad_alloc. При выделении памяти с помощью оператора new автоматически вызывается конструктор объекта.

Для того, чтобы операторы new и delete можно было перегружать в любых местах программы, их следует перегружать глобально. Например, в следующей программе операторы new и delete перегружаются для класса MyClass:

#include <iostream> #include <cstdlib> #include <new> using namespace std; class MyClass

{

int var1, var2; public: MyClass ( ) { }

MyClass (int a, int b )

{

var1 = a; var2 = b;

}

void show ( ) { cout << var1 << “ “;

cout << var2 << endl;

}

void * operator new (size_t size ); void operator delete ( void * p ); };

void * MyClass::operator new (size_t size )

{

void * p;

cout << “Внутри перегруженного оператора new “ << endl; p = malloc (size);

if (! p)

{

bad_alloc bad; throw bad;

}

return p;

}

77

void MyClass::operator delete ( void * p )

{

cout << “Внутри перегруженного оператора delete ” << endl;

free (p);

}

 

 

int main ( )

{

 

 

MyClass *p1, *p2;

 

 

try {

 

 

 

p1 = new MyClass (4, 7 );

}

catch (bad_alloc bl)

{

 

cout << “Ошибка при выделении памяти для объекта p1 ” <<endl; return 1; }

try {

p2 = new MyClass (5, 8 );

}

catch (bad_alloc bl)

{

cout << “Ошибка при выделении памяти для объекта p2 ” <<endl; return 1; }

p1->show ( );

p2->show ( ); delete p1; delete p2; return 0; }

Перегруженные операторы применяются только к тем типам, для которых они определены. Если операторы new и delete перегрузить вне классов, они будут считаться глобальными. В этом случае стандартные операторы new и delete игнорируются.

Перегрузка оператора “,”

Этот оператор является бинарным. Его смысл при перегрузке может быть произвольным. Если целью перегрузки является выполнение операции, аналогичной стандартной, следует игнорировать все аргументы, кроме крайнего справа. Именно этот параметр считается результатом стандартного оператора “,” . Пример программы, демонстрирующей перегрузку оператора “,”.

#include <iostream> using namespace std;

class MyClass

{

private:

 

int var1, var2; public:

MyClass ( ) { }

MyClass ( int a, int b ) { var1 =a; var2 = b; }

78

void show ( ) {cout << var1 << “ “<< var2 << endl; } MyClass operator + (MyClass op2);

MyClass operator , (MyClass op2) ;

};

// Перегрузка оператора “запятая “ для класса MyClass MyClass MyClass::operator , (MyClass op2)

{

MyClass temp;

 

 

temp.var1 = op2.var1;

 

temp.var2 = op2.var2;

 

cout << op2.var1 <<” “ << op2.var2 <<endl;

 

return temp;

}

 

// Перегрузка оператора + для класса MyClass

 

MyClass MyClass::operator + (MyClass op2 )

{

MyClass temp;

 

 

temp. var1 = op2. var1+ var1;

 

temp. var2 = op2. var2 + var2;

 

return temp;

 

 

}

 

 

int main ( ) {

 

 

MyClass obj1(2, 7 ) , obj2 (5, 9 ), obj3 (4 , 6 );

 

obj1.show ();

 

 

obj2. show ( );

 

 

obj3. show ( );

 

 

obj1 = ( obj1, obj2+ obj2, obj3 );

 

return 0;

}

 

Несмотря на то, что все операнды, стоящие в левой части, игнорируются, каждое выражение вычисляется компилятором. Операнд, стоящий в левой части, передается с помощью указателя this, а его значение игнорируется функцией operator , ( ) . Функция возвращает значение операнда, стоящего в правой части.

Определяемые пользователем преобразования

Можно определить собственные преобразования для объектов типа класса. Такие определенные пользователем преобразования автоматически вызываются компилятором по мере необходимости. Например, класс MyINT позволяет определять объекты, способные хранить значения от 0 до 255, и перехватывает ошибки выхода за его границы. В классе реализовано шесть операторных функций:

class MyInt {

friend operator+( const MyInt &, int ); friend operator-( const MyInt &, int ); friend operator-( int, const MyInt & );

79

friend operator+( int, const MyInt & ); public:

MyInt( int ival ) : value( ival ) { } operator+( const MyInt & ); operator-( const MyInt & );

// ...

private: int value;

}; Операторы-члены дают возможность складывать и вычитать два

объекта MyInt. Глобальные же операторы-друзья позволяют производить эти операции над объектами данного класса и объектами встроенных арифметических типов. Необходимо только шесть операторов, поскольку любой встроенный арифметический тип может быть приведен к типу int. Например, выражение

MyInt mvar( 3 ); mvar + 3.14159

разрешается в два шага:

Константа 3.14159 типа double преобразуется в целое число 3. Вызывается operator+(const MyInt &,int), который возвращает

значение 6.

Если потребуется выполнять битовые и логические операции, а также операции сравнения и составные операторы присваивания, то необходимо перегрузить очень большое количество операторов. Поэтому значительно удобнее автоматически преобразовать объект класса MyINT в объект типа int.

В языке C++ имеется механизм, позволяющий в любом классе задать набор преобразований, применимых к его объектам. Для MyInt определим приведение объекта к типу int. Вот его реализация:

class MyInt { public:

MyInt( int ival ) : value( ival ) { }

//конвертер MyInt ==> int operator int( ) { return value; }

//перегруженные операторы не нужны private:

int value; };

Выражение operator int() – это конвертер, реализующий определенное пользователем преобразование, в данном случае, приведение типа класса к заданному типу int.

80

Конвертеры

Конвертер объявляется в теле класса путем указания ключевого слова operator, за которым следует целевой тип преобразования. Общий вид конвертера следующий:

operator type();

где type может быть встроенным типом, типом класса или именем typedef. Конвертеры, в которых type – тип массива или функции, не допускаются. Конвертер должен быть функцией-членом. В его объявлении не должны задаваться ни тип возвращаемого значения, ни список параметров:

operator int( MyInt & ); // ошибка: не член class MyInt {

public:

int operator int(); // ошибка: задан тип возвращаемого значения operator int( int = 0 ); // ошибка: задан список параметров

//

}; Конвертер вызывается в результате явного преобразования типов.

Если преобразуемое значение имеет тип класса, у которого есть конвертер, и в операции приведения указан тип этого конвертера, то он и вызывается. Для объекта MyInt смысл преобразования в int заключается в том, чтобы вернуть число типа int, хранящееся в члене value.

Теперь объект класса MyInt можно использовать всюду, где допустимо использование int. Если предположить, что перегруженных операторов больше нет и в MyInt определен конвертер в int, операция сложения

MyInt mvar( 3 ); mvar + 3.14159

разрешается двумя шагами:

Вызывается конвертер класса MyInt, который возвращает целое число 3.

Целое число 3 расширяется до 3.0 и складывается с константой

двойной точности 3.14159, что дает 6.14159.

Такое поведение больше соответствует поведению операндов встроенных типов по сравнению с определенными ранее перегруженными операторами. Когда значение типа int складывается со значением типа double, то выполняется сложение двух чисел типа double (поскольку тип int расширяется до double) и результатом будет число того же типа.

В реализацию класса MyInt была добавлена поддержка новой функциональности:

class MyInt {

friend istream& operator>>( istream &is, MyInt & m ); friend ostream& operator<<( ostream &os, const MyInt & m )

81

{return os << m.value; } public:

MyInt( int i=0 ) : value( rangeCheck( i ) ){} int operator=( int i )

{return( value = rangeCheck( i ) ); } operator int() { return value; }

private:

int rangeCheck( int ); int value;

}; Определения функций-членов, находящиеся вне тела класса:

istream& operator>>( istream &is, MyInt & mi ) {

int ix;

 

is >> ix;

 

mi = ix;

// MyInt::operator=(int)

return is;

}

int MyInt::rangeCheck( int i ) {

/* если установлен хотя бы один бит, кроме первых восьми, * то значение слишком велико; сообщить и сразу выйти */ if ( i & ~0377 ) {

cerr << "\n*** Ошибка диапазона SmallInt: " << i << " ***" << endl;

exit( -1 );

}

return i; } };

В показанном ниже классе Person определено несколько конвертеров. В одном из них для задания имени типа используется typedef tName, а в другом

– тип класса MyInt. #include "MyInt.h" typedef char * tName; class Person { public:

Person ( char *, int );

operator MyInt() { return val; }

operator tName()

{ return name; }

operator int( )

{ return val; }

// другие открытые члены private:

MyInt val; char *name; };

82

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]