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

Конструктор преобразования.

Почему этот тип конструкторов выделен в отдельный класс, а не отнесен к прочим конструкторам? У конструктора преобразования есть особая семантика, которая тоже иногда настигает программиста и бьет по голове. Дело в том, что в языке С++ есть неявные преобразования, которые были оставлены для совместимости с языком Си. Неявные преобразования, это когда компилятор, вместо присваивания x=y (T1 x; T2 y;) вставляет код x=T1(y) (в Си преобразование типа выглядит иначе: x=(T1)y). В языке Си всегда были следующие неявные преобразования:

char => short => int => float => double

T* => void*

Т.к. более сложных типов в Си не было, то все было нормально. Одной из идей введения классов, было то, чтоб можно было расширять возможности языка без явных добавлений новых возможностей в базис. Например, программу на языке Fortran трудно переписать на язык Си, потому что в Си нет типа Complex. Для работы с комплексными числами, приходится писать специальные функции, и в результате выражения сильно усложняются:

A = B*C + D*(0,1); // Выражение на языке Fortran

A = Plus( Mult(B,C) , Mult(D,Im1) ); // То же самое выражение на языке Си

Разумеется, любой физик откажется писать такие выражения на Си, когда есть более удобный язык Fortran. В С++ концепция класса позволяет смоделировать комплексный тип (и не только комплексный), причем на С++ можно написать выражение, почти эквивалентное соответствующему выражению в языке Fortran. Для этого используется понятие класса, понятие функций-членов, и понятие перекрытия операций. Функции члены можно тоже перекрывать, и мы уже с этим сталкивались – это наличие нескольких конструкторов с одним и тем же именем, но с разным набором параметров. Страуструп разрешил перекрытие любых операций, за исключением трех: "?:" (условная операция), "." (операция точка), ".*" (операция взятия указателя элемента структуры). Остальные знаки операций перекрывать можно (в т.ч. операции умножения, сложения, вычитания, и даже скобки). Для структуры Complex перекрытие оператора сложения можно сделать следующим образом:

Complex operator + (Complex C1&, Complex C2&) { return Complex(C1.Re+C2.Re, C1.Im+C2.Im); };

Такие же перекрытия надо написать для других операций. Возникает проблема: если переменная С в выражении будет вещественной, то как поступить в этом случае? Можно написать, конечно, конструктор преобразования Complex(double,double), но тогда его придется вписывать явно в выражение. Можно для каждого из базисных типов данных написать свой оператор "+" и все прочие операторы, но это приводит к значительному "раздуванию" библиотек.

Лекция 12

Обсудим подробнее конструкторы преобразования. Почему они появились? Изначальной идеей Страуструпа было создать классы так, чтобы с их помощью можно было определять любые типы с произвольной семантикой. При этом новые типы ничем не отличались от базисных с точки зрения эксплуатации. Хорошим примером в данном случае будет тип комплексных чисел

A=B*C+D*(0,1)

Это выражение на языке Fortran, в нем подразумевается, что все переменные имеют тип комплексного числа. Написать подобное выражение на языке, не обладающем гибкостью C++, вообще говоря, сложно. С другой стороны можно добавлять типы данных в базис и интегрировать их с языком, но это не самое лучшее решение.

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

Мы писали уже перекрытие оператора «+» для комплексных чисел, он у нас выглядел, как функция-член. Это не совсем хорошо, но об этом мы еще поговорим.

С точки зрения языка Fortran, такое решение выглядит далеко не самым лучшим, так как в этом случае, если A,B,C,D - комплексные

Complex A,B,C,D;

(для определения константы следует воспользоваться конструктором)

Выражение на C++ примет вид:

A=B*C+D*Complex(0,1);

Вообще говоря, тут можно было бы схитрить и перекрыть операцию «,», но это ящик Пандоры, так как запятые используются не только для связывания комплексных констант. Можно поступить по-другому и описать константу следующим образом:

const Complex Im1(0,1);

Однако, что еще есть в Fortran? Там есть еще неявное преобразование типов (которое есть практически в любом языке). Естественно, в случае, если A и D – комплексные, а B и C – float, то наше выражение будет компилироваться с ошибкой. Получается, что нам следует определять операции сложения, умножения и т.д. для всех базисных типов данных. Библиотеки, таким образом будут неимоверно разрастаться. В результате Страуструп разрешил пользователю управлять неявными преобразованиями типов, так как если вспомнить философию языка C++, то основной ее тезис – сделать язык удобным для программирования, а удобство означает прежде всего – большие мощность и гибкость, чтобы человек мог сделать все то, что он хочет.

Итак, неявные преобразования программист может разрешать и составлять сам. Для этого ему и служит конструктор преобразования.

Пусть у нас есть A+B, где A и B – double числа.

+(Complex, Complex)

Если наш конструктор находит Complex(double) (а он его, конечно, найдет, правда, по хитрым правилам), то в этом случае компилятор проведет замену:

A+B => Complex(A)+Complex(B)

проблема решена. Конечно, недостаточно написать Complex(double), следует еще написать Complex(float), Complex(int) – и т.д. Тогда у нас полностью обеспечивается гибкость Fortran.

Возникает вопрос, почему бы не разрешить

1+C ~ Complex(double(1)) + C?

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