що визначаються Перетворення Типу h2>
Наведена у введенні реалізація комплексних чисел
занадто обмежена, щоб вона могла влаштувати будь-кого, тому її потрібно
розширити. Це буде в основному повторенням описаних вище методів. p>
Наприклад: p>
class complex ( p>
double re, im; p>
public: p>
complex (double
r, double i) (re = r; im = i;) p>
friend complex
operator + (complex, complex); p>
friend complex
operator + (complex, double); p>
friend complex
operator + (double, complex); p>
friend complex
operator-(complex, complex); p>
friend complex
operator-(complex, double); p>
friend complex
operator-(double, complex); p>
complex
operator-()// унарний - p>
friend complex
operator * (complex, complex); p>
friend complex
operator * (complex, double); p>
friend complex
operator * (double, complex); p>
// ... p>
); p>
Тепер, маючи опис complex, ми можемо написати: p>
void f () p>
( p>
complex
a (1,1), b (2,2), c (3,3), d (4,4), e (5,5); p>
a =-b-c; p>
b = c * 2.0 * c; p>
c = (d + e) * a; p>
) p>
Але писати функцію для кожного поєднання complex і double,
як це робилося вище для operator + (), нестерпно нудно. Крім того, близькі до
реальності засоби комплексної арифметики повинні надавати щонайменше
дюжину таких функцій; подивіться, наприклад, на тип complex. p>
Конструктори p>
Альтернативу використання декількох функцій
(перевантажених) складає опис конструктора, який по заданому double
створює complex. p>
Наприклад:
p>
class complex ( p>
// ... p>
complex (double
r) (re = r; im = 0;) p>
); p>
Конструктор, що вимагає тільки один параметр,
необов'язково викликати явно: p>
complex z1 = complex (23); p>
complex z2 = 23; p>
І z1, і z2 будуть ініціалізований викликом complex (23). p>
Конструктор - це припис, як створювати значення
даного типу. Коли потрібно значення типу, і коли таке значення може бути
створено конструктором, тоді, якщо таке значення дається для привласнення,
викликається конструктор. p>
Наприклад, клас complex можна було б описати так: p>
class complex ( p>
double re, im; p>
public: p>
complex (double
r, double i = 0) (re = r; im = i;) p>
friend complex
operator + (complex, complex); p>
friend complex
operator * (complex, complex); p>
); p>
і дії, до яких входитимуть змінні complex і
цілі константи, стали б допустимі. Ціла константа буде інтерпретуватися
як complex з нульовою уявною частиною. Наприклад, a = b * 2 означає:
p>
a = operator * (b, complex (double (2), double (0))) p>
Певний користувачем перетворення типу
застосовується неявно тільки тоді, коли воно є єдиним. p>
Об'єкт, сконструйований за допомогою явної або неявної
виклику конструктора, є автоматичним і буде знищений при першій
можливості, зазвичай відразу ж після оператора, в якому він був створений. p>
Операції Перетворення p>
Використання конструктора для завдання перетворення
типу є зручним, але має наслідки, які можуть виявитися
небажаними: p>
Не може бути неявного перетворення з певного
користувачем типу в основний тип (оскільки основні типи не є
класами); p>
Неможливо задати перетворення з нового типу в старий,
не змінюючи опис старого, і p>
Неможливо мати конструктор з одним параметром, не маючи
при цьому перетворення. p>
Останнє не є серйозною проблемою, а з першими
двома можна впоратися, визначивши для вихідного типу операцію перетворення.
Функція член X:: operator T (), де T - ім'я типу, визначає перетворення з X
в T. Наприклад, можна визначити тип tiny (крихітний), який може мати
значення тільки в діапазоні 0 ... 63, але все одно може вільно поєднуватися в
цілими в арифметичних операціях: p>
class tiny ( p>
char v; p>
int assign (int i) p>
(return v = (i & ~ 63)? (error ( "помилка діапазону"), 0): i;) p>
public: p>
tiny (int i) (assign (i);) p>
tiny (tiny & i) (v = t.v;) p>
int operator = (tiny & i) (return v = t.v;) p>
int operator = (int i) (return assign (i);) p>
operator int () (return v;) p>
) p>
Діапазон значення перевіряється завжди, коли tiny
ініціалізується int, і завжди, коли йому присвоюється int. Одне tiny може
присвоюватися іншого без перевірки діапазону. Щоб дозволити їм над
змінними tiny звичайні цілі операції, визначається tiny:: operator int (),
неявне перетворення з int в tiny. Завжди, коли в тому місці, де потрібно
int, з'являється tiny, використовується відповідне йому int. p>
Наприклад:
p>
void main () p>
( p>
tiny c1 = 2; p>
tiny c2 = 62; p>
tiny c3 = c2 - c1;// c3 = 60 p>
tiny c4 = c3;// ні перевірки діапазону (необов'язкова) p>
int i = c1 + c2;// i = 64 p>
c1 = c2 + 2 * c1;// помилка діапазону: c1 = 0 (а не 66) p>
c2 = c1-i;// помилка діапазону: c2 = 0 p>
c3 = c2;// ні перевірки діапазону (необов'язкова) p>
) p>
Тип вектор з tiny може виявитися більш корисним,
оскільки він економить простір. Щоб зробити цей тип більш зручним у
зверненні, можна використовувати операцію індексування. p>
Інше застосування визначаються операцій перетворення --
це типи, які надають нестандартні подання чисел (арифметика
по підставі 100, арифметика з фіксованою крапкою, двійковій-десяткове
представлення і т.п.). При цьому зазвичай перевизначаються такі операції, як + і
*. p>
Функції перетворення виявляються особливо корисними для
роботи зі структурами даних, коли читання (реалізоване за допомогою операції
перетворення) тривіально, в той час як присвоювання і ініціалізація помітно
більш складні. p>
Типи istream і ostream спираються на функцію
перетворення, щоб зробити можливими такі оператори, як while
(cin>> x) cout <> x вище повертає istream &. Це значення неявно
перетвориться до значення, яке вказує стан cin, а вже це значення
може перевірятися оператором while. Однак визначати перетворення з оного
типу в інший так, що при цьому втрачається інформація, звичайно не варто. p>
Неоднозначності p>
Присвоєння об'єкту (або ініціалізація об'єкта) класу X
є допустимим, якщо або присвоюється значення є X, чи існує
єдине перетворення присвоюється значення в тип X. p>
У деяких випадках значення потрібного типу може
сконструювати за допомогою декількох застосувань конструкторів або операцій
перетворення. Це повинно робитися явно; допустимо тільки один рівень неявних
перетворень, визначених користувачем. Іноді значення потрібного типу може
бути таким чином, більш ніж одним способом. Такі випадки є
неприпустимими. p>
Наприклад: p>
class x (/ * ... */X (int); x (char *);); p>
class y (/ * ... */Y (int);); p>
class z (/ * ... */Z (x);); p>
overload f; p>
x f (x); p>
y f (y); p>
z g (z); p>
f (1);// неприпустимо: неоднозначність f (x (1)) або f (y (1)) p>
f (x (1 )); p>
f (y (1 )); p>
g ( "asdf");// неприпустимо:
g (z (x ( "asdf"))) не пробує p>
g (z ( "asdf ")); p>
Певні користувачем перетворення розглядаються
тільки в тому випадку, якщо без них виклик дозволити не можна. p>
Наприклад: p>
class x (/ * ... */X (int);) p>
overload h (double), h (x); p>
h (1); p>
Виклик міг би бути проінтерпретувати або як
h (double (1)), або як h (x (1)), і був би неприпустимий за правилом єдиності.
Але перша інтерпретація використовує тільки стандартне перетворення і вона
буде обрана за правилами. Правила перетворення не є ні самими
простими для реалізації та документації, ні найбільш загальними з тих, які
можна було б розробити. Візьмемо вимога єдиності перетворення.
Більш загальний підхід дозволив би компілятору застосовувати будь-яке перетворення,
яке він зможе знайти; таким чином, не потрібно було б розглядати всі
можливі перетворення перед тим, як оголосити вираз допустимим. На жаль,
це означало б, що зміст програми залежить від того, яке перетворення було
знайдено. В результаті зміст програми якимсь чином залежав би від порядку
опису перетворення. Оскільки вони часто перебувають у різних вихідних файлах
(написаних різними людьми), зміст програми буде залежати від порядку
компонування цих частин разом. Є інший варіант - заборонити всі неявні
перетворення. Немає нічого простішого, але таке правило приведе або до неелегантність
призначеним для користувача інтерфейсів, або до бурхливого зростання перевантажених функцій, як
це було в попередньому розділі з complex. p>
Найбільш загальний підхід враховував би всю наявну інформацію про
типах і розглядав би всі можливі перетворення. Наприклад, якщо
використовувати попереднє опис, то можна було б обробити aa = f (1), так як
тип aa визначає єдиність тлумачення. Якщо aa є x, то
єдине, що дає в результаті x, який потрібно присвоюванням, - це
f (x (1)), а якщо aa - це y, то замість цього використовуватиметься f (y (1)). Самий
загальний підхід впорався б і з g ( "asdf"), оскільки єдиною
інтерпретацією цього може бути g (z (x ( "asdf "))). Складність цього
підходу в тому, що він вимагає розширеного аналізу всього вирази для того,
щоб визначити інтерпретацію кожної операції та виклику функції. Це призведе до
уповільнення компіляції, а також до викликає здивування інтерпретацій і
повідомлень про помилки, якщо компілятор розгляне перетворення, визначені
у бібліотеках і т.п. При такому підході компілятор буде брати до уваги більше,
чому, як можна очікувати, знає що пише програму програміст! p>
Список літератури h2>
Для підготовки даної роботи були використані матеріали
з сайту http://www.realcoding.net
p>