Скачать презентацию Родственники и друзья До сих Скачать презентацию Родственники и друзья До сих

L12 Дружественные функции.ppt

  • Количество слайдов: 15

Родственники и друзья • • • До сих пор мы считали операторные функции языка Родственники и друзья • • • До сих пор мы считали операторные функции языка C++ просто иначе названными собственными функциями. Но это не совсем так. Операторные функции в C++, независимо от того, для какого класса определены, сохраняют неизменными приоритет (оператор умножения * всегда выполняется раньше, чем оператор сложения +) и порядок выполнения (оператор + выполняется слева направо, независимо от того, какие объекты «складываются» ) операторов. В отличие от собственных функций допустимое число параметров различных операторных функций определяется природой реализуемых ими операторов. Например, операторная функция для оператора вызова функции (), который мы использовали в разделе «Доступ к массиву» , может, как и собственная функция, иметь сколько угодно параметров, а операторная функция для унарного (действующего на один-объект) оператора вовсе не должна иметь параметров, потому что может найти объект по указателю this. А вот операторной функции для бинарного оператора достаточно передать один объект, потому что второй (тот, на который указывает this) всегда «под рукой» .

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

 • • • • • class complex { public: complex(double г, double i) • • • • • class complex { public: complex(double г, double i) : re_(r). im_(i) {} complex(double r) : re_(r), im_(0) {} complex(): re_(0). im_(0) {} complex operator+(complex a) { complex tmp; tmp. re_ = re_ + a. re_; tmp. im_ = im_ + a. im_; return tmp; } double getre(){return re_; } double getim(){return im_; } private: double re_; double im_; }:

 • • Прежде всего обратим внимание на необычный вид конструкторов (всего их три). • • Прежде всего обратим внимание на необычный вид конструкторов (всего их три). Как и раньше, в круглых скобках показаны параметры, а после двоеточия перечислены данные и (в скобках) параметры, на эти данные влияющие. Следующая запись означает, что реальная часть комплексного числа ге станет после вызова конструктора равной г, а мнимая im — параметру i: complex(double r, double i) : ге_(г), im_(i) {} • Такая запись часто встречается в программах на C++ и ее нужно знать, хотя «старый» способ записи конструктора как обычной функции без возвращаемого значения ничуть не хуже: complex(double r, double i) { re_=r: im_=i; } • Всего в нашем классе complex три конструктора: первый устанавливает реальную и мнимую части комплексного числа, второй — только реальную, а мнимую полагает по умолчанию равной нулю, третий конструктор совсем не имеет параметров и устанавливает в нуль обе части комплексного числа.

 • • • Разобравшись с конструкторами, обратимся к операторной функции для сложения. В • • • Разобравшись с конструкторами, обратимся к операторной функции для сложения. В ней создается временный объект tmp, причем при его создании вызывается конструктор по умолчанию, обнуляющий реальную и мнимую части. Далее к реальной части tmp прибавляется реальная часть «текущего» объекта ге_ и реальная часть объекта а. ге_, передаваемого в качестве аргумента операторной функции. Мнимая часть получается аналогично, и затем весь объект tmp возвращается во внешний мир.

 • • Казалось бы, все понятно и просто. На самом же деле оператор • • Казалось бы, все понятно и просто. На самом же деле оператор + для класса complex разрушает все наши представления о доступе к секретным данным объектов. Раньше мы думали, что к области private имеют доступ только собственные функции объекта, которому и принадлежит эта область. Но взгляните еще раз на операторную функцию для оператора сложения: complex operator+(complex a){ complex tmp; tmp. re_ = re_ + a. re_; tmp. im_ = im_ + a. i. m_; return tmp; } • В этой функции ге_ и im_ — действительная и мнимая части текущего объекта, на который указывает this. А вот tmp. re_ и а. ге — действительные части чужих объектов! Причем доступ к ним идет самым непосредственным образом, без всяких собственных функций!

 • • Разгадка в том, что C++ считает все объекты одного класса близкими • • Разгадка в том, что C++ считает все объекты одного класса близкими родственниками, разрешая любому такому объекту иметь доступ ко всем закрытым частям других родственных объектов. Вот почему наш оператор работает! Но если бы мы посмели написать а. ге_ внутри функции main() или в любой функции любого объекта, не принадлежащего классу complex, компилятор выдал бы сообщение об ошибке!

 • Испытание оператора сложения #include <iostream> #include • Испытание оператора сложения #include #include "complex. hpp" using namespace std; void out(complex a) { cout<< a. getre()<<", " << a. getim() << endl; } int main() { complex a, b(3, 2), c(5, 4), d; c=b + 20; // c=b. operator+(20); out(c); // 23, 2 d= a + b + c; // d=(a. operator+(b)). operator+(c); out(d); // 26, 4 } • В этой программе результаты сложения выводит в экран функция out(). Поскольку она не принадлежит классу complex, в ней непосредственный доступ к области private объектов этого класса невозможен. Потому и приходится использовать собственные функции getre() и getim(). В программе показаны операции сложения с использованием операторов в привычной записи и (в комментариях) в виде операторных функций. Заметим, что оператор сложения независимо от определения всегда выполняется слева направо, то есть к объекту а прибавляется b и к новому объекту прибавляется с.

 • Любопытно проследить за тем, как прибавляется число 20 к объекту Ь. Поскольку • Любопытно проследить за тем, как прибавляется число 20 к объекту Ь. Поскольку оператор — это всего лишь иначе записанная операторная функция, следует ожидать, что для передачи по значению будет использован конструктор копирования. • Но так было бы при передаче объекта типа complex. У нас же передается целое число. Поэтому вызывается один из конструкторов, который преобразует целое число 20 в комплексное B(20. 0, 0. 0) и передает его оператору. • Кстати, а что будет с нашей программой, если ей предложат не к b прибавить 20 (с=Ь+20), а к 20 прибавить b — (c=20+b)? Очевидно, ничего хорошего, ведь запись 20. operator+(b) — это полная бессмыслица, и компилятор наверняка откажется работать, выдав на экран что-то вроде (нет подходящего оператора): no match for 'int + complex &'

 • • Как же поступить в этом случае? Ведь новый оператор должен во • • Как же поступить в этом случае? Ведь новый оператор должен во всем напоминать привычный оператор +, «равнодушный» к порядку слагаемых. Очевидно, операторная функция для оператора сложения должна принимать два аргумента, но как раз это и запрещено функции класса. Единственный выход в данном случае — создать операторную функцию с двумя параметрами вне класса. Увидев оператор +, соединяющий два объекта типа complex, компилятор попробует найти определение оператора + внутри класса complex. He найдя его там, он станет искать определение вне класса. Поскольку компилятор знает, что + — бинарный оператор, соединяющий два объекта, он ожидает увидеть два параметра у соответствующей операторной функции. Найдя такую функцию, компилятор преобразует al + a 2 в operator+(al, a 2). Сделает он это только в том случае, когда параметров два. Если их будет, скажем, три, то появится сообщение об ошибке.

 • • Но тут возникает другая трудность. Операторная функция, определенная вне класса, перестает • • Но тут возникает другая трудность. Операторная функция, определенная вне класса, перестает быть собственной функцией, и ей уже недоступны закрытые области объектов. В нашем случае придется написать функции setre() и setim(), чтобы извне менять реальную и мнимую части числа. С этими функциями наша операторная функция будет выглядеть следующим образом: complex operator+(complex al, complex a 2) { complex tmp; tmp. setre(al. getre() + a 2. getre()): tmp. setim(al. getim() + a 2. getim()); return tmp: } • Выглядит это, согласитесь, не очень красиво. Поэтому в языке C++ предусмотрена возможность разрешать некоторым функциям доступ к закрытым областям объекта. Такие функции называются дружественными, их прототипы помещаются внутрь класса и предваряются словом «friend» (друг).

Определение оператора + с помощью дружественной функции class complex { public: complex(double r, double Определение оператора + с помощью дружественной функции class complex { public: complex(double r, double i) : re_(r), im_(i) {} complex(double r) : re_(r), im_(0) {} Complex(): re_(0), im_(0)) {} friend complex operator+(complex al. complex a 2); double getre(){return re_; } double getim()return im_; } private: double re_; double im_; }: complex operator+(complex al, complex a 2) { complex tmp; tmp. re_ = al. re_+a 2. re_; tmp. im_ = al. im_+a 2. im_; return tmp; }

Испытание оператора +, заданного дружественной функцией #include <iostream> using namespace std; #include Испытание оператора +, заданного дружественной функцией #include using namespace std; #include "complex 1. hpp" void out(complex p) { cout << p. getre() << ", " << p. getim() << endl; } int main() { complex a, b(3, 2), c(5, 4), d; out(a); // a=(0, 0) c = b + 20; // c=(23, 2) out(c); d=a + b + c; // d=(26, 4) out(d); a = 20 + c; // a=(43, 2) РАБОТАЕТ! out(a); }

 • Только что мы использовали дружественную операторную функцию для построения оператора сложения. Но • Только что мы использовали дружественную операторную функцию для построения оператора сложения. Но хорошо ли это? Вообще говоря, не очень. Разрешая доступ извне, объект подвергает себя опасности, потому что не может полностью контролировать свои внутренние переменные. У объекта появляется как бы второй пульт управления, а кто и когда начнет нажимать на нем кнопки — неизвестно. • Дружественные функции разрушают представление об объектах, как о чем-то цельном, защищенном от внешних воздействий, и пользоваться ими нужно только в крайнем случае. Однако операторы, реализованные с помощью дружественных операторных функций, действительно очень близки объекту, поэтому в данном случае использование дружественных функций оправдано. Но вряд ли можно оправдать более свободный доступ к объекту с помощью произвольных дружественных функций (не операторных) особенно со стороны другого класса, объявленного как friend: class A { friend class В; }:

 • • • При таком объявлении В — это дружественный А класс1, то • • • При таком объявлении В — это дружественный А класс1, то есть собственные функции В имеют доступ к внутренним структурам класса А, что не только опасно, но и неудобно. Ведь изменение внутренних структур класса А с большой вероятностью скажется на его дружественном классе В, имеющем доступ к этим структурам. Иными словами, нарушается важнейший принцип надежного программирования: последствия изменений в программе должны быть как можно локальней: перемены внутренних структур класса должны (в идеале) сказываться только на устройстве его собственных функций. Но чем больше дружественных классов и функций в программе, тем больше она становится похожей на кошмарное минное поле, где разминирование в одном месте приводит к появлению нескольких новых мин, причем в самых неожиданных местах.