Реферат по предмету "Программирование"


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

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

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

Например:

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


Не сдавайте скачаную работу преподавателю!
Данный реферат Вы можете использовать для подготовки курсовых проектов.

Поделись с друзьями, за репост + 100 мильонов к студенческой карме :

Пишем реферат самостоятельно:
! Как писать рефераты
Практические рекомендации по написанию студенческих рефератов.
! План реферата Краткий список разделов, отражающий структура и порядок работы над будующим рефератом.
! Введение реферата Вводная часть работы, в которой отражается цель и обозначается список задач.
! Заключение реферата В заключении подводятся итоги, описывается была ли достигнута поставленная цель, каковы результаты.
! Оформление рефератов Методические рекомендации по грамотному оформлению работы по ГОСТ.

Читайте также:
Виды рефератов Какими бывают рефераты по своему назначению и структуре.