Переопределение стандартных операций.



Мы поможем в написании ваших работ!


Мы поможем в написании ваших работ!



Мы поможем в написании ваших работ!


ЗНАЕТЕ ЛИ ВЫ?

Переопределение стандартных операций.



Основные определения и свойства

 

В С++ есть возможность распространения действия стандартных операций на операнды абстрактных типов данных.

 

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

 

operator α,

 

где α - обозначение этой операции (например, + - | += и т.д.).

 

При этом в языке существует несколько ограничений:

· нельзя создавать новые символы операций;

· нельзя переопределять операции

 

:: * (- разыменование, не бинарное умножение) ?:

 

sizeof . .* # ##

· символ унарной операции не может использоваться для переопределения бинарной операции и наоборот. Например, символ << можно использовать только для бинарной операции, ! - только для унарной, а & - и для унарной, и для бинарной;

· переопределение операций не меняет ни их приоритетов, ни порядка их выполнения (слева направо или справа налево);

· при перегрузке операции компьютер не делает никаких предположений о ее свойствах. Это означает, что если стандартная операция += может быть выражена через операции + и =, т.е. а + = b эквивалентно а = а + b, то для переопределения операций в общем случае таких соотно-шений не существует, хотя, конечно, программист может их обеспечить. Кроме того, не делается предположений, например, о коммутативности операции +: компилятор не имеет оснований считать, что а + b, где а и b - абстрактных типов - это то же самое, что и b + a;

· никакая операция не может быть переопределена для операндов стандартных типов.

Функция operator α() является обычной функцией, которая может содержать от 0 до 2 явных аргументов. Она может быть, а может и не быть функцией-членом класса.

 

class cl { int i;

public:

int get () {return i;}

int operator + (int ); // Бинарный плюс.

};

int operator + (cl&, float); // Бинарный плюс.

 

В первой форме бинарного плюса не один, а два аргумента. Первый - неявный. Его имеет любая нестатическая функция-член класса; этот аргумент является указателем на объект, для которого она вызвана. Реализация обеих функций может выглядеть так:

int cl::operator + (int op2){

return i + op2;}

int operator + (cl &op, float op2){

return op.get() + op2;}

 

Что будет, если в глобальной функции ::operator+() второй аргумент будет иметь тип не float, а int? В этом случае компилятор выдаст сообщение об ошибке, так как он не сможет сделать выбор между функциями cl::operator + () и ::operator +() - обе подходят в равной степени.

 

Для выполнения переопределенной унарной операции αх (или хα), где х - объект некоторого абстрактного типа 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, " на велосипеде";)


31. Преобразования абстрактных типов.

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

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

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 еще не существует.


Классы и шаблоны.

Шаблон семейства классов определяет способ построения отдельных классов подобно тому, как класс определяет правила построения и формат отдельных объектов. Шаблон семейства классов определяется так:

 

template < список параметров шаблона > определение класса

 

В определении класса, входящего в шаблон, особую роль играет имя класса. Оно является не именем отдельного класса, а параметризованным именем семейства классов.

 

Определение шаблона может быть только глобальным.

 

Рассмотрим класс vector, в число данных которого входит одномерный массив. Независимо от типа элементов этого массива в классе должны быть определены одни и те же базовые операции, например, доступ к элементу по индексу и т.п. Если тип элементов вектора задать в качестве параметра шаблона класса, то система будет формировать вектор нужного типа и соответствующий класс при каждом определении конкретного объекта.

 

// Файл vec.cpp

template < class T > // Т-параметры шаблона;

class vector {

T *pv; // одномерный массив;

int size; // размер массива.

public:

vector ( int );

~vector ( ) { delete []pv; }

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

. . .

};

template < class T >

vector < T >::vector ( int n ){

pv = new T[n];

size = n;

}

// Конец файла vec.cpp

 

Когда шаблон введен, появляется возможность определить конкретные объекты конкретных классов, каждый из которых параметрически порожден из шаблона. Формат определения объекта одного из классов, порожденного шаблоном, следующий:

 

имя параметризованного класса < фактические параметры шаблона >

имя объекта (параметры конструктора).

 

В нашем случае определить вектор, имеющий 100 компонент типа double, можно так:

 

vector < double > x ( 100 );

 

Программа может выглядеть так:

 

#include < iostream.h >

#include "vec.cpp"

 

void main ( ){

vector < int > x ( 5 );

vector < char > c( 5 );

for ( int i = 0; i < 5; i++ ){

x [ i ] = i; c [ i ] = 'A' + i;}

for ( i = 0; i < 5; i++ ) cout << " " << x [ i ] << " " << c [ i ];

cout << "/n";

}

 
 
0 A 1 B 2 C 3 D 4 E


Результат:

В списке параметров шаблона могут присутствовать формальные переменные, не определяющие тип. Точнее, это параметры, для которых тип фиксирован:

 

template < class T, int size = 64 >

class row {

T * data;

int length;

public: row ( ) { length = size; data = new T [ size]; }

~row ( ) { delete T[] data; }

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

};

 

void main ( ){

row < float, 7 > rf;

row < int, 7 > ri;

for ( int i = 0; i < 7; i++ ){ rf[i] = i ; ri[i] = i * i; }

for ( i = 0; i < 8; i++ )

cout << " " << rf[i] << " " << ri[i];

cout << "\n";

}

 
 
0 0 1 1 2 4 3 9 4 16 5 25 6 36


Результат:

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



Последнее изменение этой страницы: 2016-08-15; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.235.179.79 (0.054 с.)