РефератыИнформатика, программированиеОпОпределяемое Преобразование Типа

Определяемое Преобразование Типа

Приведенная во введении реализация комплексных чисел слишком ограничена, чтобы она могла устроить кого-либо, поэтому ее нужно расширить. Это будет в основном повторением описанных выше методов.


Например:


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;

<
br />

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) cout<>x выше возвращает 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"))). Сложность этого подхода в том, что он требует расширенного анализа всего выражения для того, чтобы определить интерпретацию каждой операции и вызова функции. Это приведет к замедлению компиляции, а также к вызывающим удивление интерпретациям и сообщениям об ошибках, если компилятор рассмотрит преобразования, определенные в библиотеках и т.п. При таком подходе компилятор будет принимать во внимание больше, чем, как можно ожидать, знает пишущий программу программист!

Сохранить в соц. сетях:
Обсуждение:
comments powered by Disqus

Название реферата: Определяемое Преобразование Типа

Слов:1286
Символов:10391
Размер:20.29 Кб.