Разбор программы rational
• rational(double r) : q(BIG), a(r * BIG){}
Этот конструктор преобразует double в rational.
• operator double(){return static_cast<double>(a)/q;}
А эта функция-член преобразует rational в double.
• inline int greater(int i, int j)
{ return (i >j ? i : j);}
inline double greater(double x , double y)
{ return ( x > y ? x : y) ; }
inline rational greater(rational w, rational z)
{ return ( w > z ? w : z); }
Три различные функции перегружены. Наиболее интересная из них имеет переменные типа rational в списке аргументов и такой же возвращаемый тип. Преобразующая функция-член operator double необходима для выполнения сравнения w > z. Позже мы покажем, как перегрузить operator>(), чтобы он мог напрямую принимать типы rational.
• cout << “\ngreater(“ << i << “, “ << j << “) = “
<< greater(i, j)
cout << "\ngreater(“ << x << “, “ << у << “) = "
<< greater(x, y) ;
Первая инструкция выбирает первое определение greater, следуя правилу точного соответствия. Вторая инструкция выбирает второе определение greater, потому что используется стандартное расширяющее преобразование float в double. Тип значения переменной х расширяется до double.
• cout << “) = “ << greater(static_cast<rational>(i), z) ;
Выбирается второе определение greater следуя правилу точного соответствия. Явное преобразование переменной i к типу rational необходимо для того, чтобы избежать неопределенности.
• zmax = greater(w, z);
Это точное соответствие для третьего определения.
См. упражнение 3 на стр. 220, которое относится к программе rational.
Дружественные функции
Ключевое слово friend (друг) служит спецификатором, уточняющим свойства функции. Оно дает функции-не-члену доступ к скрытым членам класса и предоставляет способ для обхода ограничений сокрытия данных в C++. Однако должна быть веская причина для обхода этих ограничений, поскольку они важны для надежного программирования.
Одна из причин использования дружественных функции состоит в том, что некоторые функции нуждаются в привилегированном доступе к более чем одному классу. Вторая причина в том, что дружественные функции передают все свои аргументы через список аргументов, при этом каждое значение аргумента должно допускать автоматическое преобразование. Преобразования выполняются для переменных класса, передаваемых явно, и особенно полезны в случае перегрузки операторов, как мы увидим в следующем разделе.
Дружественная функция должна быть объявлена внутри объявления класса, по отношению к которому она является дружественной (с которым она дружит). Функция предваряется ключевым словом friend и может встречаться в любой час класса; это не влияет на ее смысл. Мы предпочитаем размещать объявление friend в открытой части класса. Функция-член одного класса может быть дружественной другому классу. В этом случае для указания ее имени в дружественном классе используется оператор разрешения области видимости. То, что все функции-члены одного класса являются дружественными функциями другого класса, может быть указано как friend class имя_класса.
Следующие объявления иллюстрируют синтаксис
class tweedledee {
. . . . .
friend void alice(); //дружественная функция
int cheshire(); //функция-член
. . . . .
};
class tweedledum {
. . . . .
friend int tweedledee::cheshire();
. . . . .
};
class tweedledumber {
. . . . .
friend class tweedledee; //все функции-члены
. . . . . //из tweedledee получают доступ
};
Рассмотрим класс matrix (см. раздел 6.8, «Двумерные массивы», на стр. 173) и класс vect (см. раздел 6.5, «Класс vect», на стр. 165). Функция, умножающая вектор на матрицу, как представлено в этих двух классах, могла бы быть более эффективной, если бы имела доступ к закрытым членам обоих классов. Это могла бы быть дружественная для обоих классов функция. Как обсуждалось в разделе 6.5, «Класс vect», на стр. 165, безопасный доступ к элементам vect и matrix обеспечивался функцией членом element (). Используя ее, можно написать функцию умножения, не нуждающуюся в статусе дружественной. Однако затраты в виде накладных расходов на вызов функции и проверку границ массива сделали бы такое умножение матриц весьма неэффективным.
В файле matrix2.cpp
class matrix; //предварительное объявление
class vect {
public:
friend vect mру ( const vect& v, const matrix& m);
. . . . .
private:
int* p;
int size;
};
class matrix {
public:
friend vect mpy(const vect& v, const matrix& m) ;
. . . . .
private:
int** p;
int s1, s2;
};
vect mpy(const vect& v, const matrix& m)
{
assert(v.size = = m.sl); //проверка размеров
//использует привелегированный доступ к р в обоих классах
vect ans(m.s2) ;
int i , j ;
for (i = 0; i <= m.ub2(); ++i) {
ans . p [ i ] = 0 ;
for (j = 0; j <= m.ub1(); ++j)
ans.p[i] += v.p[j] * m.p[j][i];
}
return ans;
}
Небольшой нюанс: необходимо предварительное объявление класса matrix, поскольку функция mру () должна присутствовать в обоих классах и она использует каждый из классов как тип аргумента.
Парадигма ООП состоит в том, что доступ к объектам (в C++ ими являются переменные класса) должен осуществляться через открытые члены. Только функции-члены класса должны иметь доступ к скрытой реализации АТД. Это четкий и строгий принцип разработки. Дружественные функции создают некую двойственность. Они имеют доступ к закрытым членам, не являясь при этом функциями-членами. Их можно использовать для быстрых исправлений в коде, которому необходим доступ к реализации деталей класса. Но такой механизм легко неправильно использовать.