友元类
友元类是指能够访问另一个类的 private
和 protected
成员的类。
示例:
#include <iostream>
class Storage
{
private:
int m_nValue{};
double m_dValue{};
public:
Storage(int nValue, double dValue)
: m_nValue{ nValue }, m_dValue{ dValue }
{ }
// 将 Display 声明为 Storage 的友元类
friend class Display;
};
class Display
{
private:
bool m_displayIntFirst{};
public:
explicit Display(bool displayIntFirst)
: m_displayIntFirst{ displayIntFirst }
{ }
// 由于 Display 是 Storage 的友元,其成员可直接访问 Storage 的私有成员
void displayStorage(const Storage& storage)
{
if (m_displayIntFirst)
std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
else
std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
}
void setDisplayIntFirst(bool b)
{
m_displayIntFirst = b;
}
};
int main()
{
Storage storage{ 5, 6.7 };
Display display{ false };
display.displayStorage(storage); // 6.7 5
display.setDisplayIntFirst(true);
display.displayStorage(storage); // 5 6.7
return 0;
}
输出:
6.7 5
5 6.7
补充说明:
- 即使
Display
是Storage
的友元,也无法访问Storage
对象的*this
指针(因为*this
是函数形参的一部分)。 - 友元关系非对称:
Display
是Storage
的友元,并不意味着Storage
是Display
的友元;若需双向友元,双方必须各自声明。 - 友元关系非传递:若 A 是 B 的友元,B 是 C 的友元,并不表示 A 是 C 的友元。
- 友元关系不继承:若 A 声明 B 为友元,B 的派生类不会自动成为 A 的友元。
- 友元类声明同时起到前向声明作用,无需额外前向声明即可使用。
友元成员函数
除了将整个类设为友元,也可仅授予某个成员函数访问权。语法与友元非成员函数类似,只需使用成员函数完整限定名。
然而,实践中有一定复杂度:
#include <iostream>
class Display; // Display 的前向声明
class Storage
{
private:
int m_nValue{};
double m_dValue{};
public:
Storage(int nValue, double dValue)
: m_nValue{ nValue }, m_dValue{ dValue }
{ }
// 试图将 Display::displayStorage 设为友元
friend void Display::displayStorage(const Storage& storage); // 错误:尚未看到 Display 的完整定义
};
class Display
{
private:
bool m_displayIntFirst{};
public:
explicit Display(bool displayIntFirst)
: m_displayIntFirst{ displayIntFirst }
{ }
void displayStorage(const Storage& storage) // 编译错误:尚未看到 Storage 的完整定义
{
if (m_displayIntFirst)
std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
else
std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
}
};
上述代码无法编译:
- 将成员函数设为友元,编译器必须已看到其所属类的完整定义;
- 同时,成员函数参数若使用
Storage
类型,又需Storage
的完整定义,造成循环依赖。
解决方案:
- 先完整定义
Display
; - 前向声明
Storage
; - 在
Storage
定义中将Display::displayStorage
设为友元; - 最后给出
Display::displayStorage
的类外定义。
完整代码:
#include <iostream>
class Storage; // 前向声明
class Display
{
private:
bool m_displayIntFirst{};
public:
explicit Display(bool displayIntFirst)
: m_displayIntFirst{ displayIntFirst }
{ }
void displayStorage(const Storage& storage); // 声明,引用 Storage 需前向声明
};
class Storage
{
private:
int m_nValue{};
double m_dValue{};
public:
Storage(int nValue, double dValue)
: m_nValue{ nValue }, m_dValue{ dValue }
{ }
friend void Display::displayStorage(const Storage& storage); // 友元声明
};
// 类外定义 Display::displayStorage
void Display::displayStorage(const Storage& storage)
{
if (m_displayIntFirst)
std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
else
std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
}
int main()
{
Storage storage{ 5, 6.7 };
Display display{ false };
display.displayStorage(storage);
return 0;
}
若将每个类分别放入独立头文件(Display.h
、Storage.h
)及对应源文件,再统一编译,则无需上述调整顺序,代码更清晰。
习题
在几何学中,点(Point) 是空间中的位置,可由 3 个坐标 x, y, z
表示,例如 Point{2.0, 1.0, 0.0}
。
在物理学中,向量(Vector) 是具有大小与方向的量,可由 3 个轴向分量 x, y, z
表示,例如 Vector{2.0, 0.0, 0.0}
表示沿 x 轴正方向、长度为 2 的向量。
将向量加至点可得到新位置,如 Point{2.0, 1.0, 0.0} + Vector{2.0, 0.0, 0.0} → Point{4.0, 1.0, 0.0}
。
在计算机图形学中,此类点和向量常用于表示顶点及形变。
给定程序框架:
#include <iostream>
class Vector3d
{
private:
double m_x{}, m_y{}, m_z{};
public:
Vector3d(double x, double y, double z)
: m_x{ x }, m_y{ y }, m_z{ z } { }
void print() const
{
std::cout << "Vector(" << m_x << ", " << m_y << ", " << m_z << ")\n";
}
};
class Point3d
{
private:
double m_x{}, m_y{}, m_z{};
public:
Point3d(double x, double y, double z)
: m_x{ x }, m_y{ y }, m_z{ z } { }
void print() const
{
std::cout << "Point(" << m_x << ", " << m_y << ", " << m_z << ")\n";
}
void moveByVector(const Vector3d& v)
{
// 请将本函数实现为 Vector3d 的友元
}
};
int main()
{
Point3d p{ 1.0, 2.0, 3.0 };
Vector3d v{ 2.0, 2.0, -3.0 };
p.print();
p.moveByVector(v);
p.print();
return 0;
}
步骤 1
将 Point3d
设为 Vector3d
的友元类,并实现 Point3d::moveByVector()
。
(答案略)
步骤 2
不再将 Point3d
整个类设为友元,而是仅将成员函数 Point3d::moveByVector
设为 Vector3d
的友元。
(答案略)
步骤 3
将步骤 2 的解法拆分为 5 个文件:Point3d.h
、Point3d.cpp
、Vector3d.h
、Vector3d.cpp
、main.cpp
。
(答案略)