Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ЯМП ответы.doc
Скачиваний:
0
Добавлен:
01.04.2025
Размер:
710.66 Кб
Скачать

23.Совместное использование.

Если в некоторой области действия имеется несколько различных объявлений функций с одним именем, это имя называется совместно используемым. Когда употребляется такое имя, нужная функция выбирается путём сравнения типов фактических параметров с типами формальных параметров.

double abs(double x);

int abs(int x);

abs(1);

abs(1.0);

// Вызов int abs(int x)

// Вызов double abs(double x)

Поскольку для всякого типа Т типы Т и Т& допускают совпадающие множества инициализирующих значений, функции с типами параметров, различающимися только в этом отношении, не могут иметь одно имя.

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

Различные версии совместно используемой функции-члена класса могут предоставлять различныеправа доступа.

class Buffer

{ private:

char *p;

int size;

protected:

Buffer(int s, char *np) { size = s; p = np; }

public:

Buffer(int s) { p = new char[size = s]; } ... };

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

  • точное соответствие типов, т.е. полное соответствие или соответствие, достигаемое тривиальными преобразованиями типов (например, имя массива и указатель, имя функции и указатель на функцию, типы T и const T);

  • соответствие, достигаемое «продвижением» интегральных типов (например, bool в int, char в int, short вint) и float в double;

  • соответствие, достигаемое путем стандартных преобразований (например, int в double, double в int, doubleв long double, указателей на производные типы в указатели на базовые, указателей на произвольные типы в void*, int в unsigned int);

  • соответствие, достигаемое при помощи преобразований, определяемых пользователем;

  • соответствие за счет многоточий в объявлении функции.

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

Результат перегрузки не зависит от порядка объявления функций.Альтернативой перегрузке является использование нескольких функций с различными именами и разными типами аргументов. При этом приходится помнить несколько имен и то, как их правильно использовать. Кроме того, при отсутствии перегрузки к аргументам функций применяются все стандартные преобразования, что также может привести к дополнительным ошибкам.Функции, объявленные в различных областях видимости (не пространствах имён), не являются перегруженными.

void f(int);

void g()

{ void f(double);

f(1); }

// Вызов f(double), хотя 1 – целая константа

24.Перегрузка операций.

В каждой технической области – и в большинстве не технических – имеются свои стандартные обозначения, облегчающие представление и обсуждение часто встречающихся концепций. Например, благодаря постоянному использованию, выражение x + y * z яснее для нас, чем фраза умножить y на z и прибавить результат к x. Трудно переоценить значение краткой и выразительной формы записи типичных операций.

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

Большинство операций языка С могут использоваться совместно (быть перегружены). Для этого в объявлении класса необходимо объявить функцию, имеющую следующее имя:<тип> operator <операция> (<операнды>)

Не могут использоваться совместно операции    .   .*   ::   ?:

И унарная, и бинарная формы операций    +   –   *   &    могут использоваться совместно.

Функция-оператор должна либо быть функцией-членом класса, либо иметь хотя бы один параметр некоторого класса или ссылки на класс. Префиксная унарная операция может быть объявлена как нестатическая функция-член класса без параметров либо как функция, не являющаяся членом класса, с одним параметром. Бинарная операция может быть объявлена либо как нестатическая функция-член с одним параметром, либо как обычная функция (не член класса) с двумя параметрами.Для отличия префиксного и постфиксного вариантов операции функция, реализующая постфиксный вариант, объявляется с дополнительным параметром типа int.Недопустимо и невозможно менять старшинство, ассоциативность и число операндов у операции.

Совместно используемая операция не может иметь параметров с умолчаниями.По поводу определяемых пользователем операций делается всего несколько предположений. В частности, operator=, operator(), operator[] и operator-> должны быть нестатическими функциями-членами класса – это гарантирует, что их первый операнд будет l-значением . Второй параметр (индекс) функцииoperator[] может быть любого типа. Это делает возможным определение векторов, ассоциативных массивов и т.д.Тождества, верные для операций над основными типами (например, ++а ~ а += 1), не обязаны выполняться в отношении операций над «классовыми» типами. Такая связь не сохраняется в операциях, определяемых пользователем, если только пользователь не позаботиться об этом сам. Компилятор не сгенерирует определение X::operator+= из определений X::operator+ и X::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; }

// Ошибка – operator= является приватным

// Ошибка – operator& является приватным

// Ошибка – operator, является приватным

С другой стороны, операторам можно придать новый смысл, задав соответствующие определения.

Рассматрим пример определения и использования перегруженных операторов.

class Complex

{ private:

double r, m;

public:

Complex(double nr = 0, double nm = 0) : r(nr), m(nm) { }

Complex operator ++();

Complex operator ++(int);

Complex operator + (const Complex& c) const; };

Complex Complex::operator ++()

{ ++r; return *this; }

Complex Complex::operator ++(int)

{ Complex x = *this;

r++;

return x; }

Complex Complex::operator + (const Complex& c) const

{ return Complex(r + c.r, m + c.m); }

void main()

{ Complex a(0, 0), b(2, 2), c;

++a;

a++;

c = a + b;

c = a + ++b; }

// Все операции объявляются как члены класса

// Префиксная операция ++

// Постфиксная операция ++

// Объект класса надо возвращать, чтобы было возможно вкладывать вызов операций ++

// (как префиксной, так и постфиксной) в другие операторы

// Префиксная операция ++ возвращает новое значение операнда,

// в то время, как постфиксная операция ++ возвращает старое значение операнда

// Эквивалентно a.operator++()

// Эквивалентно a.operator++(0)

// Эквивалентно a.operator+(b)

Аргументы операций были определены как константные ссылки. Это позволяет пользоваться выражениями, включающими в себя обычные арифметические операторы, без интенсивного копирования. Для класса Complex это, возможно, не даёт значительного выигрыша во времени, но для больших объектов дело обстоит иначе. Указателями в качестве аргументов нельзя воспользоваться потому, что невозможно заместить смысл оператора, применяемого к указателю.Возвращение ссылки может также показаться эффективным решением.

Complex& operator + (const Complex& c);

Это допустимо, но вызывает проблемы при выделении памяти. Так как ссылка на результат будет выдана вызываемой функций как ссылка на значение, возвращаемое из функции, значение не может быть автоматической переменной. Так как оператор может использоваться более одного раза в выражении результат не может быть статической локальной переменной. Результат, как правило, будет размещаться в свободной памяти. Копирование возвращаемого значения часто обходится дешевле (в смысле времени выполнения, размера кода и данных), чем выделение и освобождение памяти под объект. Кроме того, это легче запрограммировать.Предпочтительно сводить к минимуму количество функций, непосредственно манипулирующих представлением объекта. Этого можно добиться объявлением в теле самого класса только тех операций, которые должны модифицировать значение первого аргумента, таких как +=. Операции, которые просто выдают новое значение на основе своих аргументов, такие как +, определяются вне класса и используют операции, имеющие доступ к представлению.

class Complex

{ private:

double r, m;

public:

Complex(double nr = 0, double nm = 0) : r(nr), m(nm) { }

Complex operator + (const Complex& c);

// Требует доступа к представлению

};

Complex Complex::operator += (const Complex& c)

{ r += c.r;

m += c.m;

return *this; }

Complex operator + (const Complex&c1, const Complex& c2)

{ Complex x = c1;

return x += c2; }

// Доступ к представлению при помощи +=

void main()

{ Complex a(0, 0), b(2, 2), c(7, -5);

Complex r1 = a + b + c;

Complex r2 = a;

r2 += b;

r2 += c; }

// r1 = operator+(a, operator+(b, c))

// r2 = a

// r2.operator+=(b)

// r2.operator+=(c)

За исключением разницы во времени вычисления r1 и r2 эквивалентны.Составные операторы присваивания, такие как += и *=, обычно легче определить, чем их «простые» части+ и *. Это сначала удивляет, но данный вывод следует из того факта, что в операции + участвуют три объекта (два операнда и результат), а в операции += – только два. В последнем случае в результате избавления от временных переменных улучшается производительность во время выполнения. Кроме того, такие функции (в простых случаях) легко делаются встраиваемыми.