- •Классы и заголовочные файлы Отделение объявления от реализации
- •Int m_month;
- •Int m_year;
- •Int getValue () { return m_value ; }
- •Int getValue () {return m_value;}
- •Классы и заголовочные файлы
- •Int getDay () {return m_day;}
- •Void Date::SetDate (int day, int month, int year) {
- •Параметры по умолчанию
- •Библиотеки
- •Заключение
- •Классы и const
- •Константные объекты классов
- •Int m_value;
- •Int getValue () { return m_value ; }
- •Int main () {
- •Константные методы классов
- •Int m_value;
- •Int m_value;
- •Int m_value;
- •Константные ссылки и классы
- •Int getDay () {return m_day;}
- •Void printDate (const Date &date) {
- •Int main () {
- •Int m_month;
- •Int m_year;
- •Void setDate (int day, int month, int year) {
- •Перегрузка константных и неконстантных функций
- •Int main () {
- •Статические переменные-члены класса
- •Статические переменные-члены класса
- •Int generateId () {
- •Int main () {
- •Int main () {
- •Int main () {
- •Статические члены не связаны с объектами класса
- •Int main () {
- •Определение и инициализация статических переменных-членов класса
- •Инициализация статических переменных-членов внутри тела класса
- •Использование статических переменных-членов класса
- •Int getId() const { return m_id; }
- •Int main () {
- •Статические методы класса
- •Int main () {
- •Int main () {
- •Статические методы не имеют указателя this
- •Int main () {
- •Предупреждение о классах со всеми статическими членами
- •Int main () {
- •Дружественные функции и классы
- •Int m_value;
- •Void reset (Anything &anything) {
- •Int main () {
- •Int m_value;
- •Дружественные функции и несколько классов
- •Дружественные классы
- •Примечания о дружественных классах:
- •Дружественные методы
- •If (m_displayIntFirst)
- •Int m_intValue;
- •Values (int intValue, double dValue) {
- •Анонимные объекты
- •Int main () {
- •Анонимные объекты класса
- •Int m_dollars;
- •Int getDollars () const {return m_dollars;}
- •Int main () {
- •Int m_dollars;
- •Int getDollars() const { return m_dollars; }
- •Int main () {
- •Вложенные типы данных в классах
- •Int main () {
- •Вложенные пользовательские типы данных в классах
- •Int main () {
- •Другие вложенные пользовательские типы данных в классах
Дружественные методы
Вместо того, чтобы делать дружественным целый класс, мы можем сделать дружественными только определенные методы класса.
Их объявление аналогично объявлениям обычных дружественных функций, за исключением имени метода с имяКласса:: префиксом в начале (например, Display::displayItem()).
Переделаем наш предыдущий пример, чтобы метод Display::displayItem () был дружественным классу Values.
Мы могли бы сделать следующее:
class Display; // предварительное объявление класса Display
class Values {
private:
int m_intValue;
double m_dValue;
public:
Values (int intValue, double dValue) {
m_intValue = intValue;
m_dValue = dValue;
}
// Делаем метод Display::displayItem () другом класса Values
friend void Display::displayItem(Values& value); // ошибка: Values //не видит полного определения класса Display
};
class Display {
private:
bool m_displayIntFirst;
public:
Display (bool displayIntFirst) {m_displayIntFirst = displayIntFirst;}
void displayItem (Values &value) {
if (m_displayIntFirst)
std::cout << value.m_intValue << " " << value.m_dValue << '\n';
else // или выводим сначала double
std::cout << value.m_dValue << " " << value.m_intValue << '\n';
}
};
Однако это не сработает!
Чтобы сделать метод дружественным классу, компилятор должен увидеть полное определение класса, в котором дружественный метод определяется (а не только лишь его прототип).
Поскольку компилятор, прочёсывая последовательно строчки кода не увидел полного определения класса Display, но успел увидеть прототип его метода, то он выдаст ошибку в строке определения этого метода дружественным классу Values.
Можно попытаться переместить определение класса Display выше определения класса Values:
class Display {
private:
bool m_displayIntFirst;
public:
Display (bool displayIntFirst) {m_displayIntFirst = displayIntFirst;} {
void displayItem (Values &value) // ошибка: Компилятор не знает, //что такое Values
If (m_displayIntFirst)
std::cout << value.m_intValue << " " << value.m_dValue << '\n';
else // или выводим сначала double
std::cout << value.m_dValue << " " << value.m_intValue << '\n';
}
};
class Values {
private:
Int m_intValue;
double m_dValue;
public:
Values (int intValue, double dValue) {
m_intValue = intValue;
m_dValue = dValue;
}
// Делаем метод Display::displayItem() другом класса Values
friend void Display::displayItem(Values& value);
};
Однако теперь мы имеем другую проблему. Поскольку метод Display::displayItem() использует ссылку на объект класса Values в качестве параметра, а мы только что перенесли определение Display выше определения Values, то компилятор будет жаловаться, что он не знает, что такое Values. Получается замкнутый круг.
Это также можно очень легко решить:
Во-первых, для класса Values используем предварительное объявление.
Во-вторых, переносим определение метода Display::displayItem() за пределы класса Display и размещаем его после полного определения класса Values.
Вот как это будет выглядеть:
#include <iostream>
class Values; // предварительное объявление класса Values
class Display {
private:
bool m_displayIntFirst;
public:
Display (bool displayIntFirst) { m_displayIntFirst = displayIntFirst;
}
void displayItem (Values &value); // предварительное объявление, //приведенное выше, требуется для этой строки
};
class Values {
// полное определение класса Values
private:
int m_intValue;
double m_dValue;
public:
Values (int intValue, double dValue) {
m_intValue = intValue;
m_dValue = dValue;
}
// Делаем метод Display::displayItem () другом класса Values
friend void Display::displayItem (Values& value);
};
// Теперь мы можем определить метод Display::displayItem (), которому //требуется увидеть полное определение класса Values
void Display::displayItem (Values &value) {
if (m_displayIntFirst)
std::cout << value.m_intValue << " " << value.m_dValue << '\n';
else // или выводим сначала double
std::cout << value.m_dValue << " " << value.m_intValue << '\n';
}
int main () {
Values value (7, 8.4);
Display display(false);
display.displayItem (value);
return 0;
}
Теперь всё будет работать правильно. Хотя это может показаться несколько сложным, но перемещение классов и методов нужно только потому, что мы пытаемся сделать всё в одном файле.
Лучшим решением было бы поместить каждое определение класса в отдельный заголовочный файл с определениями методов в соответствующих файлах .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).
Точки и векторы часто используются в компьютерной графике (точка для представления вершин фигуры, а векторы — для перемещения фигуры).
Исходя из следующей программы:
#include <iostream>
class Vector3D {
private:
double m_x, m_y, m_z;
public:
Vector3D (double x = 0.0, double y = 0.0, double z = 0.0)
: m_x(x), m_y(y), m_z(z) {}
void print () {
std::cout << "Vector(" << m_x << " , " << m_y << " , " << m_z << ")\n";
}
};
class Point3D {
private:
double m_x, m_y, m_z;
public:
Point3D(double x = 0.0, double y = 0.0, double z = 0.0)
: m_x(x), m_y(y), m_z(z) {}
void print() {
std::cout << "Point(" << m_x << " , " << m_y << " , " << m_z << ")\n";
}
void moveByVector (const Vector3D &v) {
// Реализуйте эту функцию как дружественную классу Vector3D
}
};
int main () {
Point3D p (3.0, 4.0, 5.0);
Vector3D v(3.0, 3.0, -2.0);
p.print();
p.moveByVector(v);
p.print();
return 0;
}
a) Сделайте класс Point3D дружественным классу Vector3D и реализуйте метод moveByVector () в классе Point3D.
b) Вместо того, чтобы класс Point3D был дружественным классу Vector3D, сделайте метод Point3D::moveByVector() дружественным классу Vector3D.
c) Переделайте свой ответ из задания b, используя 5 отдельных файлов: Point3D.h, Point3D.cpp, Vector3D.h, Vector3D.cpp и main.cpp.
