Программирование >>  Поддержка объектно-ориентированного программирования 

1 ... 57 58 59 [ 60 ] 61 62 63 ... 120


operator int();

void g(real a)

double d = a; int i = a; d = a;

i = a;

d = a.doubleO;

i = a.intO;

d = a.doubleO;

i = a.intO;

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

7.4 Литералы

Для классов нельзя определить литеральные значения, подобному тому как 1.2 и 12e3 являются литералами типа double. Однако, для интерпретации значений классов могут использоваться вместо функций-членов литералы основных типов. Общим средством для построения таких значений служат конструкторы с единственным параметром. Если конструктор достаточно простой и реализуется подстановкой, вполне разумно представлять его вызов как литерал. Например, с учетом описания класса complex в <complex.h> в выражении zz1*3+zz2*complex(1,2) произойдет два вызова функций, а не пять. Две операции * приведут к вызову функции, а операция + и вызовы конструктора для построения complex(3) и complex(1 ,2) будут реализованы подстановкой.

7.5 Большие объекты

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

class matrix { double m[4][4]; public:

matrix();

friend matrix operator+(const matrix&, const matrix&); friend matrix operator*(const matrix&, const matrix&);

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

matrix operator+(const matrix& arg1, const& arg2)

matrix sum;

for (int i = 0; i<4; i++) for (int j=0; j<4;

sum.m[i] [j] = arg1.m[i][j] + arg2.m[i][j]; return sum;

Здесь в функции operator+() операнды выбираются по ссылке, а возвращается само значение объекта. Более эффективным решением был бы возврат тоже ссылки:

class matrix {



friend matrix& operator+(const matrix&, const matrix&); friend matrix& operator*(const matrix&, const matrix&);

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

7.6 Присваивание и инициализация

Рассмотрим простой строковый класс string:

struct string { char* p;

int size; размер вектора, на который указывает p

string(int size) { p = new char[size=sz]; } ~string() { delete p; }

Строка - это структура данных, содержащая указатель на вектор символов и размер этого вектора. Вектор создается конструктором и удаляется деструктором. Но как мы видели в $$5.5.1 здесь могут возникнуть проблемы:

void f()

string s1(10); string s2(20) s1 = s2;

Здесь будут размещены два символьных вектора, но в результате присваивания s1 = s2 указатель на один из них будет уничтожен, и заменится копией второго. По выходе из f() будет вызван для s1 и s2 деструктор, который дважды удалит один и тот же вектор, результаты чего по всей видимости будут плачевны. Для решения этой проблемы нужно определить соответствующее присваивание объектов типа string:

struct string { char* p;

int size; размер вектора, на который указывает p string(int size) { p = new char[size=sz]; } ~string() { delete p; } string& operator=(const string&);

string& string::operator=(const string& a)

if (this !=&a) { опасно, когда s=s delete p;

p = new char[size=a.size]; strcpy(p,a.p);

return *this;

При таком определении string предыдущий пример пройдет как задумано. Но после небольшого изменения в f() проблема возникает снова, но в ином обличии:



инициализация, а не присваивание

Теперь только один объект типа string строится конструктором string::string(int), а уничтожаться будет две строки. Дело в том, что пользовательская операция присваивания не применяется к неинициализированному объекту. Достаточно взглянуть на функцию string::operator(), чтобы понять причину этого: указатель p будет тогда иметь неопределенное, по сути случайное значение. Как правило, в операции присваивания предполагается, что ее параметры проинициализированы. Для инициализации типа той, что приведена в этом примере это не так по определению. Следовательно, чтобы справиться с инициализацией нужна похожая, но своя функция:

struct string { char* p;

int size; размер вектора,

string(int size) { p = new char[size=sz]; }

~string() { delete p; }

string& operator=(const string&);

string(const string&);

string::string(const string& a)

на который указывает p

p=new char[size=sz]; strcpy(p,a.p);

Инициализация объекта типа X происходит с помощью конструктора X(const X&). Мы не перестаем повторять , что присваивание и инициализация являются разными операциями . Особенно это важно в тех случаях, когда определен деструктор. Если в классе X есть нетривиальный деструктор, например, производящий освобождение объекта в свободной памяти, вероятнее всего, в этом классе потребуется полный набор функций, чтобы избежать копирования объектов по членам:

class X {

...

X(something);

X(const X&);

operator=(const X&);

~X();

конструктор, создающий объект

конструктор копирования

присваивание:

удаление и копирование

деструктор, удаляющий объект

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

string g(string arg)

return arg;

main()

string s = asdf ;

s = g(s);

Очевидно, после вызова g() значение s должно быть asdf . Не трудно записать в параметр s копию

void f()

string s1(10); string s2 = s1;



1 ... 57 58 59 [ 60 ] 61 62 63 ... 120

© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки.
Яндекс.Метрика