Урок 126. Дружественные функции и классы

   ⁄ 

 Обновлено 22 Мар 2018  ⁄ 

⁄   451

В предыдущих уроках мы говорили о том, что данные вашего класса должны быть private. Однако может возникнуть ситуация, когда у вас есть класс и функция, которая работает с этим классом, но которая не находится в его теле. Например, есть класс, в котором хранятся данные, и функция (или другой класс), которая выводит эти данные на экран. Хотя код класса и код функции вывода разделены (для упрощения поддержки кода), код функции вывода тесно связан с данными класса. Следовательно, сделав члены класса private, мы желаемого эффекта не добьемся.

В таких ситуациях есть два варианта:

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

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

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

Дружественная функция — это функция, которая имеет доступ к закрытым членам класса, как если бы она сама была членом этого класса. Во всех других отношениях дружественная функция является обычной функцией. Ею может быть как обычная функция, так и метод другого класса. Для объявления дружественной функции используется ключевое слово friend перед прототипом функции, которую вы хотите сделать дружественной классу. Неважно, объявляете ли вы её в public или private зоне класса. Например:

Здесь мы объявили функцию reset(), которая принимает объект класса Anything и устанавливает m_value значение 0. Поскольку reset() не является членом класса Anything, то в обычной ситуации reset() не имел бы доступа к закрытым членам Anything. Однако, поскольку эта функция является дружественной классу Anything, она имеет доступ к закрытым членам Anything.

Обратите внимание, мы должны передавать объект Anything в reset() в качестве параметра. Это связано с тем, что reset() не является методом класса. Он не имеет указателя this * и кроме как передачи объекта, он не сможет взаимодействовать с классом.



Еще один пример:

Здесь мы объявили функцию isEqual() дружественной классу Something. isEqual() принимает в качестве параметров два объекта Something. Поскольку isEqual() является другом класса Something, то функция имеет доступ ко всем закрытым членам объектов класса Something. isEqual() сравнивает два объекта по значению переменной-члена и возвращает true, если они равны.

Дружественная функция сразу нескольким классам

Функция может быть другом сразу для нескольких классов. Например:

Здесь есть две вещи, на которые следует обратить внимание. Во-первых, поскольку outWeather является другом для обоих классов, то он имеет доступ к закрытым членам обоих классов. Во-вторых, обратите внимание на первую строчку в этом примере:

Это прототип класса, который сообщает компилятору, что мы определим класс Humidity позже. Без этой строчки компилятор выдал бы ошибку, что не знает, что такое Humidity при анализе прототипа дружественной функции outWeather() внутри класса Temperature. Прототипы классов выполняют ту же роль, что и прототипы функций — они сообщают компилятору об объектах, которые позднее будут определены, но которые сейчас нужно использовать. Однако, в отличие от функций, классы не имеют типа возврата или параметров, поэтому их прототипы предельно лаконичны: ключевое слово class + имя класса + ; (class Anything;).

Дружественные классы

Один класс можно сделать дружественным другому классу. Это откроет всем членам первого класса доступ к закрытым членам второго класса. Например:

Поскольку класс Display является другом класса Values, то любой из членов Display имеет доступ к private членам Values. Результат выполнения программы выше:

8.4 7

Несколько дополнительных примечаний о дружественных классах:

  Во-первых, даже несмотря на то, что Display является другом Values, Display не имеет прямой доступ к указателю this * объектов Values.

  Во-вторых, даже если Display является другом Values, это не означает, что Values также является другом Display. Если вы хотите сделать оба класса дружественными, то каждый из них должен указать в качестве друга противоположный класс. Наконец, если класс A является другом B, а B является другом C, то это не означает, что A является другом C.

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

Дружественные методы

Вместо того, чтобы делать дружественным целый класс, мы можем сделать дружественными только определенные методы класса. Их объявление аналогично объявлениям обычных дружественных функций, за исключением имени метода с префиксом className:: в начале (например, Display::displayItem).

Переделаем наш предыдущий пример, чтобы метод Display::displayItem был дружественным классу Values. Мы могли бы сделать следующее:

Однако, это не сработает. Чтобы сделать метод дружественным классу, компилятор должен увидеть полное определение класса, в котором дружественный метод определяется (а не только лишь его прототип). Поскольку компилятор, прочесывая последовательно строчки кода, не увидел полного определения класса Display, но успел увидеть прототип его метода, то он выдаст ошибку в строчке определения этого метода дружественным классу Values (строка 16).

Можно попытаться переместить определение класса Display выше определения класса Values:

Однако теперь мы имеем другую проблему. Поскольку метод Display::displayItem() использует ссылку на объект Values в качестве параметра, а мы только что перенесли определение Display выше определения Values, то компилятор будет жаловаться, что он не знает, что такое Values. Получается замкнутый круг.

К счастью, это также можно решить за пару простых шагов:



  Во-первых, для класса Values используем предварительное объявление.

  Во-вторых, переносим определение Display::displayItem() за пределы Display и размещаем его после полного определения класса Values.

Вот как это будет выглядеть:

Теперь всё будет работать правильно. Хотя это может показаться несколько сложным, но этот танец нужен только потому, что мы пытаемся сделать всё в одном файле. Лучшим решением было бы поместить каждое определение класса в отдельный заголовочный файл с определениями методов в соответствующих файлах .cpp (детальнее об этом здесь). Таким образом, все определения классов стали бы видны сразу во всех файлах .cpp, и никакого танца с перемещениями не понадобилось бы!

Итого

Дружественная функция/класс — это функция/класс, которые имеют доступ к закрытым членам другого класса, как если бы они сами были членами этого класса. Это позволяет функции/классу работать в тесном контакте с другим классом, не заставляя другого класса делать открытыми свои закрытые члены.

Тест

Точка в геометрии — это позиция в пространстве. Мы можем определить точку в 3D-пространстве как набор координат x, y и z. Например, Point(0.0, 1.0, 2.0) будет точкой в ​​координатном пространстве x = 0.0, y = 1.0 и z = 2.0.

Вектор в физике — это величина, которая имеет длину и направление (но не положение). Мы можем определить вектор в 3D-пространстве через значения x, y и z, представляющие направление вектора вдоль осей x, y и z. Например, Vector(1.0, 0.0, 0.0) будет вектором, представляющим направление только вдоль положительной оси x длиной 1.0.

Вектор может применятся к Точке для перемещения Точки на новую позицию. Это делается путем добавления направления вектора к позиции точки. Например, Point(0.0, 1.0, 2.0) + Vector(0.0, 2.0, 0.0) даст Точку (0.0, 3.0, 2.0).

Точки и Векторы часто используются в компьютерной графике (точка для представления вершин фигуры, а векторы — для перемещения фигуры).

Исходя из следующей программы:

a) Сделайте класс Point3D дружественным классу Vector3D и реализуйте метод moveByVector() в классе Point3D.

Ответ a)

b) Вместо того, чтобы класс Point3D был дружественным классу Vector3D, сделайте метод Point3D::moveByVector дружественным классу Vector3D.

Ответ b)

c) Переделайте свой ответ из задания b, используя 5 отдельных файлов: Point3D.h, Point3D.cpp, Vector3D.h, Vector3D.cpp и main.cpp.

Ответ c)

Point3D.h:

Point3D.cpp:

Vector3D.h:

Vector3D.cpp:

main.cpp:

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (7 оценок, среднее: 5,00 из 5)
Загрузка...
Подписаться на обновления:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

ПОДПИСЫВАЙТЕСЬ

НА КАНАЛ RAVESLI В TELEGRAM

@ravesli

ПОДПИСАТЬСЯ БЕСПЛАТНО