Приведенная РІРѕ введении реализация комплексных чисел слишком ограничена, чтобы РѕРЅР° могла устроить РєРѕРіРѕ-либо, поэтому ее РЅСѓР¶РЅРѕ расширить. Рто будет РІ РѕСЃРЅРѕРІРЅРѕРј повторением описанных выше методов.
Например:
class complex {
В double re, im;
public:
В complex(double r, double i) { re=r; im=i; }
В friend complex operator+(complex, complex);
В friend complex operator+(complex, double);
В friend complex operator+(double, complex);
В friend complex operator-(complex, complex);
В friend complex operator-(complex, double);
В friend complex operator-(double, complex);
 complex operator-()    // унарный -
В friend complex operator*(complex, complex);
В friend complex operator*(complex, double);
В friend complex operator*(double, complex);
В // ...
};
Теперь, имея описание complex, мы можем написать:
void f()
{
В complex a(1,1), b(2,2), c(3,3), d(4,4), e(5,5);
В a = -b-c;
В b = c*2.0*c;
В c = (d+e)*a;
}
Но писать функцию для каждого сочетания complex и double, как это делалось выше для operator+(), невыносимо нудно. Кроме того, близкие к реальности средства комплексной арифметики должны предоставлять по меньшей мере дюжину таких функций; посмотрите, например, на тип complex.
Конструкторы
Альтернативу использованию нескольких функций (перегруженных) составляет описание конструктора, который по заданному double создает complex.
Например:
class complex {
В // ...
В complex(double r) { re=r; im=0; }
};
Конструктор, требующий только один параметр, необязательно вызывать явно:
complex z1 = complex(23);
complex z2 = 23;
Рz1, и z2 будут инициализированы вызовом complex(23).
Конструктор - это предписание, как создавать значение данного типа. Когда требуется значение типа, и когда такое значение может быть создано конструктором, тогда, если такое значение дается для присваивания, вызывается конструктор.
Например, класс complex можно было бы описать так:
class complex {
В double re, im;
public:
В complex(double r, double i = 0) { re=r; im=i; }
В friend complex operator+(complex, complex);
В friend complex operator*(complex, complex);
};
и действия, в которые будут входить переменные complex и целые константы, стали бы допустимы. Целая константа будет интерпретироваться как complex с нулевой мнимой частью. Например, a=b*2 означает:
a=operator*( b, complex( double(2), double(0) ) )
Определенное пользователем преобразование типа применяется неявно только тогда, когда оно является единственным.
Объект, сконструированный с помощью явного или неявного вызова конструктора, является автоматическим и будет уничтожен при первой возможности, обычно сразу же после оператора, в котором он был создан.
Операции Преобразования
Рспользование конструктора для задания преобразования типа является удобным, РЅРѕ имеет следствия, которые РјРѕРіСѓС‚ оказаться нежелательными:
Не может быть неявного преобразования из определенного пользователем типа в основной тип (поскольку основные типы не являются классами);
Невозможно задать преобразование из нового типа в старый, не изменяя описание старого; и
Невозможно иметь конструктор с одним параметром, не имея при этом преобразования.
Последнее не является серьезной проблемой, а с первыми двумя можно справиться, определив для исходного типа операцию преобразования. Функция член X::operator T(), где T - имя типа, определяет преобразование из X в T. Например, можно определить тип tiny (крошечный), который может иметь значение только в диапазоне 0...63, но все равно может свободно сочетаться в целыми в арифметических операциях:
class tiny {
char v;
int assign(int i)
{ return v = (i&~63) ? (error("ошибка диапазона"),0) : i; }
public:
tiny(int i) { assign(i); }
tiny(tiny& i) { v = t.v; }
int operator=(tiny& i) { return v = t.v; }
int operator=(int i) { return assign(i); }
operator int() { return v; }
}
Диапазон значения проверяется всегда, когда tiny инициализируется int, и всегда, когда ему присваивается int. Одно tiny может присваиваться другому без проверки диапазона. Чтобы разрешить выполнять над переменными tiny обычные целые операции, определяется tiny::operator int(), неявное преобразование из int в tiny. Всегда, когда в том месте, где требуется int, появляется tiny, используется соответствующее ему int.
Например:
void main()
{
tiny c1 = 2;
tiny c2 = 62;
tiny c3 = c2 - c1; // c3 = 60
tiny c4 = c3; // нет проверки диапазона (необязательна)
int i = c1 + c2; // i = 64
c1 = c2 + 2 * c1; // ошибка диапазона: c1 = 0 (а не 66)
c2 = c1 -i; // ошибка диапазона: c2 = 0
c3 = c2; // нет проверки диапазона (необязательна)
}
Тип вектор из tiny может оказаться более полезным, поскольку он экономит пространство. Чтобы сделать этот тип более удобным в обращении, можно использовать операцию индексирования.
Другое применение определяемых операций преобразования - это типы, которые предоставляют нестандартные представления чисел (арифметика по основанию 100, арифметика с фиксированной точкой, двоично-десятичное представление и т.п.). При этом обычно переопределяются такие операции, как + и *.
Функции преобразования оказываются особенно полезными для работы со структурами данных, когда чтение (реализованное посредством операции преобразования) тривиально, в то время как присваивание и инициализация заметно более сложны.
РўРёРїС‹ istream Рё ostream опираются РЅР° функцию преобразования, чтобы сделать возможными такие операторы, как while (cin>>x) coutx выше возвращает istream&. Рто значение неявно преобразуется Рє значению, которое указывает состояние cin, Р° СѓР¶Рµ это значение может проверяться оператором while . Однако определять преобразование РёР· РѕРЅРѕРіРѕ типа РІ РґСЂСѓРіРѕР№ так, что РїСЂРё этом теряется информация, обычно РЅРµ стоит.
Неоднозначности
Присваивание объекту (или инициализация объекта) класса X является допустимым, если или присваиваемое значение является X, или существует единственное преобразование присваиваемого значения в тип X.
Р’ некоторых случаях значение РЅСѓР¶РЅРѕРіРѕ типа может сконструироваться СЃ помощью нескольких применений конструкторов или операций преобразования. Рто должно делаться СЏРІРЅРѕ; допустим только РѕРґРёРЅ уровень неявных преобразований, определенных пользователем. РРЅРѕРіРґР° значение РЅСѓР¶РЅРѕРіРѕ типа может быть сконструировано более чем РѕРґРЅРёРј СЃРїРѕСЃРѕР±РѕРј. Такие случаи являются недопустимыми.
Например:
class x { /* ... */ x(int); x(char*); };
class y { /* ... */ y(int); };
class z { /* ... */ z(x); };
overload f;
x f(x);
y f(y);
z g(z);
f(1); // недопустимо: неоднозначность f(x(1)) или f(y(1))
f(x(1));
f(y(1));
g("asdf"); // недопустимо: g(z(x("asdf"))) не пробуется
g(z("asdf"));
Определенные пользователем преобразования рассматриваются только в том случае, если без них вызов разрешить нельзя.
Например:
class x { /* ... */ x(int); }
overload h(double), h(x);
h(1);
Вызов мог бы быть проинтерпретирован или как h(double(1)), или как h(x(1)), и был бы недопустим по правилу единственности. Но первая интерпретация использует только стандартное преобразование и она будет выбрана по правилам. Правила преобразования не являются ни самыми простыми для реализации и документации, ни наиболее общими из тех, которые можно было бы разработать. Возьмем требование единственности преобразования. Более общий подход разрешил бы компилятору применять любое преобразование, которое он сможет найти; таким образом, не нужно было бы рассматривать все возможные преобразования перед тем, как объявить выражение допустимым. К сожалению, это означало бы, что смысл программы зависит от того, какое преобразование было найдено. В результате смысл программы неким образом зависел бы от порядка описания преобразования. Поскольку они часто находятся в разных исходных файлах (написанных разными людьми), смысл программы будет зависеть от порядка компоновки этих частей вместе. Есть другой вариант - запретить все неявные преобразования. Нет ничего проще, но такое правило приведет либо к неэлегантным пользовательским интерфейсам, либо к бурному росту перегруженных функций, как это было в предыдущем разделе с complex.
Самый общий РїРѕРґС…РѕРґ учитывал Р±С‹ РІСЃСЋ имеющуюся информацию Рѕ типах Рё рассматривал Р±С‹ РІСЃРµ возможные преобразования. Например, если использовать предыдущее описание, то РјРѕР¶РЅРѕ было Р±С‹ обработать aa=f(1), так как тип aa определяет единственность толкования. Если aa является x, то единственное, дающее РІ результате x, который требуется присваиванием, - это f(x(1)), Р° если aa - это y, то вместо этого будет использоваться f(y(1)). Самый общий РїРѕРґС…РѕРґ справился Р±С‹ Рё СЃ g("asdf"), поскольку единственной интерпретацией этого может быть g(z(x("asdf"))). Сложность этого РїРѕРґС…РѕРґР° РІ том, что РѕРЅ требует расширенного анализа всего выражения для того, чтобы определить интерпретацию каждой операции Рё вызова функции. Рто приведет Рє замедлению компиляции, Р° также Рє вызывающим удивление интерпретациям Рё сообщениям РѕР± ошибках, если компилятор рассмотрит преобразования, определенные РІ библиотеках Рё С‚.Рї. РџСЂРё таком РїРѕРґС…РѕРґРµ компилятор будет принимать РІРѕ внимание больше, чем, как РјРѕР¶РЅРѕ ожидать, знает пишущий программу программист!
Список литературы
Для подготовки данной работы были использованы материалы с сайта http://www.realcoding.net