WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!


Pages:     | 1 |   ...   | 9 | 10 || 12 | 13 |   ...   | 17 |

Для выполнения переопределенной унарной операции х (или х), где х – объект некоторого абстрактного типа Class, компилятор пробует найти либо функцию Class::operator (void), либо ::operator (Class). Если найдены одновременно оба варианта, то фиксируется ошибка.

Интерпретация выражения осуществляется либо как x.operator (void), либо как operator (x).

Для выполнения переопределенной бинарной операции x y, где х обязательно является объектом абстрактного типа Class, компилятор ищет либо функцию Class::operator (type y), либо функцию ::operatr (Class, type y), причем type может быть как стандартным, так и абстрактным типом.

Интерпретируется выражение x y либо как x.operator (y), либо как operator (x, y).

Как для унарной, так и для бинарной операции число аргументов функции operator () должно точно соответствовать числу операндов этой операци. Заметим, что часто удобно передавать значения параметров в функцию operator () не по значению, а по ссылке.

Рассмотрим для примера операцию сложения, определенную над классом “комплектное число“:

class complex { double re, im;

public:

double & real () { return re; } double & imag () { return im; } //...

};

complex operator + (complex a, complex b){ complex result;

result.real () = a.real () + b.real ();

result.imag () = a.imag () + b.imag ();

return result;} Здесь оба аргумента функции operator + () передаются по значению, то есть выполняется копирование четырех чисел типа double. Подобные затраты могут оказаться слишком накладными, особенно если операция переопределяется над таким, например, классом, как “матрица“.

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

complex operator + (complex* a, complex *b){...} Но так поступать нельзя, так как оба аргумента теперь являются объектами стандартного типа – указателями, а переопределение операций для стандартных типов запрещено.

В этой ситуации необходимо использовать ссылки – они не изменяют тип операндов, а только влияют на механизм передачи параметров:

complex operator + (complex &a, complex &b){ complex result;

resul.real () = a.real () + b.real ();

result.imag () = a.imag () + b.imag ();

return result;

} Тело функции operator + () при этом не изменилось.

Пример: определение операции + для класса stroka:

class stroka { char *c; // Указатель на строку.

int len; // Длина строки.

public:

stroka (int N = 80): len (0) // Строка, не содержащая информацию;

{i = new char [N +1]; // выделение памяти для массива.

c[0] = ‘\0‘;

} // Конструктор выделяет память для строки и делает ее пустой.

stroka (const char * arg){ len = strlen (arg);

c = new char [len + 1];

strcpy (с, arg);

} int & len_str () // Возвращает ссылку на длину строки.

{return len;} char * string ( ) // Возвращает указатель на строку.

{return с;} void display () // Печать информации о строке.

{cout << “Длина строки: “<< len << “.\n“;

cout << “Содержимое строки: “ << с << “.\n“;

} ~ stroka (){delete c;} };

stroka & operator + (stroka &a, stroka &b){ int ii = a.len_str() + b.len_str(); // Длина строки – результата.

stroka * ps = new stroka (ii);

strcpy (ps->string (), a.string ()); // Копирует строку из а;

strcat ( ps->string (), b.string ()); // присоединяет строку из b;

ps->len_str() = ii; // записывает значение длины строки;

return *ps; // возвращает новый объект stroka.

} void main () { stroka X (“Вася“);

stroka Y (“ едет“);

stroka Z;

Z = X + Y + “ на велосипеде“;

Z.display ();

} Результат выполнения программы:

Длина строки: 23.

Содержимое строки: Вася едет на велосипеде.

Заметим, что вместо Z = X+ Y + “на велосипеде“ возможна и такая форма обращения к operator +():

Z = operator + (X, Y);

Z = operator + (Z, “ на велосипеде“);

21.2. Операции new и delete при работе с абстрактными типами Операции new и delete реализуются через функции и вне зависимости от того, описаны или нет operator new() и operator delete как static, они всегда являются статическими функциями. Операция new предопределена для любого типа, в том числе и для абстрактного типа, определенного через механизм классов. Можно переопределять как глобальную функцию operator new (), так и функцию class x::operator new ().

Глобальные new и delete переопределяются обычным образом через механизм соответствия сигнатур.

Как и при переопределении глобальной функции operator new (), переопределенная функция classX::operator new () должна возвращать результат типа void*, а ее первый аргумент должен иметь тип size_t (то есть unsigned), в которой хранится размер выделяемой памяти. Замети, что при использовании операции new этот аргумент не указывается, а размер необходимого участка памяти вычисляется автоматически исходя из указанного типа.

21.3. Использование new при создании динамического объекта абстрактного типа Рассмотрим фрагмент:

class C{...

public:

C (int arg ){...} };

...

C * cp = new C (3);

Создание динамического объекта типа С можно разбить на две стадии:

Собственно создание объекта – это выполняет конструктор.

Размещение этого объекта в определенной области памяти – это делает операция new.

При этом вначале выполняется функция operator new (), а затем уже конструктор размещает создаваемый объект в выделенной памяти.



Операцию new можно переопределить:

class cl{...

public:

cl () {cout << “Конструктор класса cl.\n“;} void* operator new (unsigned);

};

void* cl::operator new (unsigned size){ cout <<“Функция operator new () класса cl;\n“;

void* p = new char [size ]; // Глобальная new! if (p) return p; else { cout << “Нет памяти для объекта типа cl!;\n“; exit(1);} } void main (){ cl * cp = new cl;} Результат:

Функция operator new () класса cl;

Конструктор класса cl.

21.4. Операция delete Выполнение операции delete применительно к указателю на объект абстрактного типа приводит к вызову деструктора для этого объекта.

сl * clp = new cl (5); // Вызов конструктора cl (5);

...

delete clp; // вызов деструктора ~ cl () перед освобождением // динамической памяти.

Функцию x::operator delete () можно переопределить в классе x, причем она может иметь только две формы:

void operator delete (void * );

void operator delete (void *, size_ t );

Если присутствует вторая форма данной операци, то компилятор использует именно ее.

21.5. Преобразование типов Преобразование типов можно разделить на 4 группы:

1) стандартный к стандартному;

2) стандартный к абстрактному;

3) абстрактный к стандартному;

4) абстрактный к абстрактному.

Первые преобразования уже были нами рассмотрены. Преобразования второй группы основаны на использовании конструкторов – как явном, так и неявном.

Снова рассмотрим касс complex:

class complex { double re, im;

public: complex (double r = 0, double i = 0){ re = r; im = i ; }...

};

Объявления вида complex c1;

complex c2 (1.8);

complex c3 (1.2, 3.7);

обеспечивают создание комплексных чисел.

Но конструктор может вызываться и неявно, в том случае, когда в выражении должен находиться операнд типа complex, а на самом деле присутствует операнд типа double:

complex operator + (complex & op, complex & op2 );

complex operator – (complex & op, complex & op2 );

complex operator * (complex & op, complex & op2 );

complex operator / (complex & op, complex & op2 );

complex operator – (complex & op ); // Унарный минус.

complex res;

res = – (c1 + 2) * c2 / 3 +.5 * c3;

Интерпретация, например, выражения –(c1 + 2) будет следующей:

operator –((operator + (c1, complex ( double (2)))).

При выполнении этого выражения неявные вызовы конструкторов создадут временные константы типа complex: (2.0, 0.0), (3.0, 0.0), (4.5, 0.0), которые будут уничтожены сразу же после того, как в них отпадет надобность. Заметим, что здесь не только происходит неявный вызов конструктора complex, но и неявное стандартное преобразование значения типа int к типу double. Число уровней неявных преобразований ограничено. При этом правила таковы: компилятор может выполнить не более одного неявного стандартного преобразования и не более одного неявного преобразования, определенного программистом.

Пример:

сlass A{ public:

A (double d){...} };

class B{ public:

B (A va){...} };

class C{ public:

C (B vb){...} };

A var1 (1.2); // A(double) B var2 (3.4); // B(A(double)) B var3 (var1); // B(A) C var4 (var3); // C(B) C var5 (var1); // C(B(A)) C var6 (5.6); // Ошибка! Неявно вызывается C(B(A(double))) C var7 (A(5.6)); // C(B(A)) Ошибка при создании переменной var6 связана с тем, что необходимо два уровня неявных нестандартных преобразований, выполняющихся с помощью вызова конструкторов: double к А, а затем A к В.

При создании переменной var7 одно из этих преобразований – double к А – сделано явным, и теперь все будет нормально.

Таким образом, конструктор с одним аргументом Class::Class(type) всегда определяет преобразование типа type к типу Class, а не только способ создания объекта при явном обращении к нему.

Для преобразования абстрактного типа к стандартному или абстрактного к абстрактному в С++ существует средство – функция, выполняющая преобразование типов, или оператор–функция преобразования типов.

Она имеет вид Class::operator type (void);

Эта функция выполняет определенное пользователем преобразование типа Class к типу type. Эта функция должна быть членом класса Class и не иметь аргументов. Кроме того, в ее объявлении не указывается тип возвращаемого значения. Обращение к этой функции может быть как явным, так и неявным. Для выполнения явного преобразования можно использовать как традиционную, так и «функциональную» форму.

Пример 1:

class X { int a, b;

public:

X (X & vx) { a = vx.a; b = vx.b; } Х (int i, int j) { a = 2*i, b = 3*j; } operator double () { return (a + b)/2.0; } // Преобразование // абстрактного типа к стандартному. };

int i = 5;

double d1 = double (i); // Явное преобразование типа int к double;

double d2 = i ; // неявное преобразование int к double;

X xv (5, -8);

double d3 = double (xv); // явное преобразование типа Х к double;

double d4 = xv; // неявное преобразование Х к double.

Пример 2:

// Преобразование абстрактного типа к абстрактному.

class Y { char * str1; // Строки str1 и str2 хранят символьное char * str2; // представление целых чисел.

public:

Y (char *s1, char *s2): str1(s1), str2 (s2){} operator X() { return X (atoi (str1), atoi (str2));} };





...

Y yvar (“12“,“-25“);

X xvar = yvar;

При создании переменной xvar перед вызовом конструктора копирования X::X(X&) будет выполнено неявное преобразование значения переменной yvar к типу Х. Это же преобразование в явном виде может выглядеть так:

X xvar = X (yvar);

X xvar = (X) yvar;

Для выражения X xvar = X (“12“, “-25“);

компилятор выдаст сообщение об ошибке «не найден конструктор с указанными аргументами». Дело в том, что в отличие от конструктора, оператор-функция преобразования типа не может создать объект абстрактного типа. Она способна только выполнить преобразование значения уже созданного объекта одного типа к значению другого типа. В последнем же примере объект типа Y еще не существует.

22. Некоторые особенности переопределенных операций Ограничениями при переопределении операций =, [], (), -> является то, что реализующие их функции operator=() и т.д. обязательно должны являться членами класса и не могут быть статическими функциями. Если говорить о механизме наследования, то обычно производный класс наследует все свойства класса базового. Из этого правила есть два исключения.

1) Производный класс не может наследовать конструкторы своего базового класса.

2) Операция присваивания, переопределенная для базового класса, не считается переопределенной для его производных классов.

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

22.1. Операция = Операция присваивания = является предопределенной для любого абстрактного типа данных.

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

struct memberone { int i;

memberone & operator = (memberone & a){ cout << “Операция копирования класса memberone\n\n“;

return a;

} };

struct membertwo { int j;

membertwo & operator = (membertwo & a){ cout << “Операция копирования класса membertwo\n\n“;

return a; } };

struct contain { int k;

memberone mo;

membertwo mt;

};

void main () { contain from;

from.mo.i = 1;

from. mt.j = 2;

from.k = 3;

contain to;

to.mo.i = 0;

to.mt.j = 0;

to.k = 0;

to = from;

cout << “to.mo.i = “ << to.mo.i << “\n\n“ << “to.mt.j = “ << to.mt.j << “\n\n“ << “to.k = “ << to.k << “\n\n“;} Результат работы программы:

Операция копирования класса memberone Операция копирования класса membertwo to.mo.i = to.mt.j = to.k = Пример 2.

Рассмотрим снова класс stroka:

class stroka { char *c;

int len;

public:

...

stroka & operator = ( stroka & str );

...

};

stroka & stroka::operator = (stroka & str) { if ( str.len > len ){ cout << “Длина строки мала! Копирование невозможно!\n“;} else { strcpy ( c, str.c ); len = str.len;} return * this;

} void main ( ) { stroka A (“Строка A“), B (“Строка“), C(“Str“);

A = B; A.display ( );

B = C; B.display ( );

C = A; C.display ( );

} В результате выполнения этой программы на мониторе появится:

Длина строки: Содержание строки: Строка Длина строки: Содержание строки: Str Длина строки мала! Копирование невозможно! Длина строки: Содержание строки: Str 22.2. Операция [ ] Выражение x [ y ], где x – объект абстрактного типа Class, интерпретируется как x.operator [ ] (y).

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

Даже если array – массив элементов абстрактного типа Class, выражение array [i] по-прежнему означает *(array + i), вне зависимости от того, переопределена операция [ ] для типа Class или нет.

Пример:

class A { int a [10];

public:

A(){ for ( int i = 0; i < 10; i ++ ) a [i] = i + 1; } int operator [ ] ( int j ) { return a [j]; } };

void main ( ) { A array [20];

cout << “array [3][5] = “ << array [3][5] << “.\n“;

} Результатом работы программы будет: array [3][5] = 6.

Очевидно, что операция [ ], использованная в конструкторе класса А, является стандартной, так как она выполняется над именем массива.

Рассмотрим теперь выражение array [3][5]. Результат его вычисления является таким, как и ожидалось, по следующей причине: операция [ ] выполняется слева направо. Следовательно, выражение array[3][5] интерпретируется как (array [3]).operator [ ](5).

Первая из двух операций [ ] является стандартной, так как выполняется над именем массива. При этом неважно, какой тип имеют его элементы.

Вторая операция [ ] – переопределенная, так как результатом первой операции [ ] является объект типа А.

Встает вопрос: когда имеет смысл переопределять операцию [ ] Попробуем создать АТД, который можно было бы использовать в программе подобно массиву.

Чтобы создание такого типа имело смысл, необходимо преодолеть основные недостатки, свойственные обычным массивам С++, а именно:

необходимость задания размера массива на стадии компиляции;

отсутствие контроля выхода за границы массива;

невозможность задания произвольных границ изменения индекса;

Pages:     | 1 |   ...   | 9 | 10 || 12 | 13 |   ...   | 17 |










© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.