第 20 节 课件: 友元及友元相关内容
目录
- 友元简介
- 关键字
friend
- 友元函数
- 定义友元函数
- 友元函数的权限
- 友元函数的特性与示例
- 友元函数的优缺点
- 友元类
- 定义友元类
- 友元类的特性与示例
- 友元关系的特性(单向性、非传递性)
- 友元的继承
- 友元的访问权限继承
- 友元继承示例
- 友元与继承的关系
- 运算符重载友元
- 友元作为运算符重载函数
- 示例:友元运算符重载
- 作业
- 自定义立方体类,用友元函数访问它的私有成员
- 自定义立方体类,用友元函数实现立方体相加
- 总结与注意事项
友元简介
友元(friend)是 C++ 里特定的定义,用于扩展类的访问权限。普通情况下,类的私有属性和方法是不能被外部直接访问的。但通过声明友元函数或友元类,可以使这些函数或类可以访问当前类的私有属性和方法。
友元分为友元函数和友元类:
友元函数(Function Friend):独立函数或类中的函数可以作为另一个类的友元函数,从而访问该类的私有及保护成员。
友元类(Class Friend):一个完整的类可以作为另一个类的友元类,能够访问被友元声明的类中的所有私有、受保护成员。
关键字 friend
friend
关键字用来指定友元关系:
class MyClass {
friend void myFriendFunction(MyClass &obj); // 友元函数声明
friend class MyFriendClass; // 友元类声明
};
友元函数和友元类的具体应用在接下来的部分详细讲解。
1. 友元函数
1.1 定义友元函数
可以通过 friend
关键字在类中声明一个函数为友元函数,从而使该函数能够访问该类的私有和受保护成员。友元函数可以是类的成员函数或者非成员函数。非成员友元函数既不是类的成员,也不是类对象的一部分。
定义友元函数的方法如下:
class Box {
private:
double width;
public:
Box(double w) : width(w) {}
friend void printWidth(Box& b); // friend 声明友元函数
};
void printWidth(Box& b) {
std::cout << "Width of box: " << b.width << std::endl; // 访问私有成员
}
int main() {
Box box(10.0);
printWidth(box);
return 0;
}
1.2 友元函数的权限
友元函数拥有类内所有权限,可以访问私有和受保护成员:
- 友元函数的声明可以在类的任意访问权限区域内声明。
- 建议将所有友元函数集中到一起声明,提高代码可读性。
1.3 友元函数的特性与示例
友元函数并不是类的成员函数,虽然它们被编译器认为是类的朋友。以下是一个完整的代码示例解释友元函数的特性:
class Cuboid {
private:
double length, width, height;
public:
Cuboid(double l, double w, double h) : length(l), width(w), height(h) {}
// 友元函数声明
friend double calculateVolume(const Cuboid& c);
friend void updateDimensions(Cuboid& c, double l, double w, double h);
};
// 友元函数定义
double calculateVolume(const Cuboid& c) {
return c.length * c.width * c.height; // 访问私有成员
}
void updateDimensions(Cuboid& c, double l, double w, double h) {
c.length = l;
c.width = w;
c.height = h; // 修改私有成员
}
int main() {
Cuboid c1(2.0, 3.0, 4.0);
std::cout << "Initial Volume of c1: " << calculateVolume(c1) << std::endl;
updateDimensions(c1, 5.0, 5.0, 5.0);
std::cout << "Updated Volume of c1: " << calculateVolume(c1) << std::endl;
return 0;
}
输出结果:
Initial Volume of c1: 24
Updated Volume of c1: 125
1.4 友元函数要点
- 友元函数不是该类的成员函数。
- 它在类的任意位置都可声明,与类的访问权限(
private
,public
,protected
)无关。 - 友元函数通常被集中声明,以提高代码结构清晰度。
1.5 友元函数的优缺点
优点:
- 可以方便地访问类的私有成员,简化某些操作(例如运算符重载)。
- 可以实现一些特殊的功能,例如两个类需要互相访问对方的私有成员。
缺点:
- 破坏了类的封装性,增加了类之间的耦合度。
- 如果滥用友元,会使代码难以维护和理解。
2. 友元类
2.1 定义友元类
友元类是一个类,它可以访问另一个类的私有和受保护成员。友元类的声明方法如下:
class ClassA {
private:
int privateMember;
public:
ClassA() : privateMember(42) {}
friend class ClassB; // ClassB 将是 ClassA 的友元类
};
class ClassB {
public:
void displayPrivateMember(const ClassA& a) {
std::cout << "Private member of ClassA: " << a.privateMember << std::endl;
}
};
int main() {
ClassA a;
ClassB b;
b.displayPrivateMember(a);
return 0;
}
2.2 友元类的特性与示例
友元类拥有被友元声明类的所有访问权限。举例如下:
class Circle {
private:
double radius;
public:
Circle(double r) : radius(r) {}
friend class Geometry; // Geometry 类是 Circle 类的友元类
};
class Geometry {
public:
double calculateArea(const Circle& c) {
return 3.14159 * c.radius * c.radius; // 访问 Circle 类的私有成员
}
void updateRadius(Circle& c, double newRadius) {
c.radius = newRadius; // 修改 Circle 类的私有成员
}
};
int main() {
Circle c(5.0);
Geometry g;
std::cout << "Initial Area: " << g.calculateArea(c) << std::endl;
g.updateRadius(c, 8.0);
std::cout << "Updated Area: " << g.calculateArea(c) << std::endl;
return 0;
}
输出结果:
Initial Area: 78.5397
Updated Area: 201.062
2.3 友元类强调
友元关系的特性
单向性(Unidirectional): 如果
ClassA
是ClassB
的友元类,ClassB
并不是ClassA
的友元类。 友元关系不具有对称性。非传递性(Non-transitive): 如果
ClassA
是ClassB
的友元,ClassB
是ClassC
的友元,这并不意味着ClassA
是ClassC
的友元。
3. 友元的继承
3.1 友元的访问权限继承
友元访问权限只限于子类中父类部分私有和受保护成员,子类新添加的成员无法被父类友元访问。
3.2 友元继承示例
class Parent {
private:
int parentPrivate;
protected:
int parentProtected;
public:
Parent() : parentPrivate(10), parentProtected(20) {}
friend void friendFunction(Parent& p);
};
void friendFunction(Parent& p) {
std::cout << "Parent Private: " << p.parentPrivate << std::endl;
std::cout << "Parent Protected: " << p.parentProtected << std::endl;
}
class Child : public Parent {
private:
int childPrivate;
public:
Child() : Parent(), childPrivate(30) {}
// Note: friendFunction() inherits permission to access Parent's members
friend void childFriendFunction(Child& c);
};
void childFriendFunction(Child& c) {
// 可以访问父类的私有和受保护部分
friendFunction(c);
// 仅可以访问子类的受保护成员,无法访问子类私有成员
//std::cout << "Child Private: " << c.childPrivate << std::endl; //如果取消注释,这行代码会报错
std::cout << "Parent Protected Access via Child: " << c.parentProtected << std::endl;
}
int main() {
Child c;
childFriendFunction(c);
return 0;
}
输出结果
Parent Private: 10
Parent Protected: 20
Parent Protected Access via Child: 20
3.3 友元与继承的关系
- 友元关系不能被继承。 如果类
A
是类B
的友元,B
的子类C
不会自动成为A
的友元。 - 基类的友元可以访问派生类对象中从基类继承来的成员,但不能访问派生类中新增的成员。
4. 运算符重载友元
4.1 友元作为运算符重载函数
运算符重载可以通过友元函数来完成,尤其是需要操作两个不同类对象的操作时。举例来说,重载 +
运算符使其能相加两个类对象:
#include <iostream>
// 自定义二维向量类
class Vector2 {
private:
int x, y;
public:
Vector2(int x = 0, int y = 0) : x(x), y(y) {} // 默认构造函数
// 运算符重载友元函数
friend Vector2 operator+(const Vector2& a, const Vector2& b);
void display() const {
std::cout << "Vector2(" << x << ", " << y << ")\n";
}
};
// 使用友元函数进行 `+` 运算符重载
Vector2 operator+(const Vector2& a, const Vector2& b) {
return Vector2(a.x + b.x, a.y + b.y);
}
int main() {
Vector2 v1(1, 2);
Vector2 v2(3, 4);
Vector2 result = v1 + v2;
std::cout << "Result: ";
result.display();
return 0;
}
输出结果:
Result: Vector2(4, 6)
4.2 示例:友元运算符重载
让我们继续使用自定义立方体类的状况,以重载 <<
和 +
运算符。
#include <iostream>
class Cuboid {
private:
double length, width, height;
public:
Cuboid(double l, double w, double h) : length(l), width(w), height(h) {}
// 友元函数重载 `+` 运算符
friend Cuboid operator+(const Cuboid& a, const Cuboid& b);
// 友元函数重载 `<<` 运算符
friend std::ostream& operator<<(std::ostream& os, const Cuboid& c);
};
// 重载 `+` 运算符
Cuboid operator+(const Cuboid& a, const Cuboid& b) {
double newLength = a.length + b.length;
double newWidth = std::max(a.width, b.width);
double newHeight = std::max(a.height, b.height);
return Cuboid(newLength, newWidth, newHeight);
}
// 重载 `<<` 运算符
std::ostream& operator<<(std::ostream& os, const Cuboid& c) {
os << "Cuboid(" << c.length << ", " << c.width << ", " << c.height << ")";
return os;
}
int main() {
Cuboid c1(3.0, 2.0, 1.0);
Cuboid c2(4.0, 1.0, 5.0);
Cuboid result = c1 + c2;
std::cout << "Cuboid 1: " << c1 << "\n";
std::cout << "Cuboid 2: " << c2 << "\n";
std::cout << "Result: " << result << "\n";
return 0;
}
输出结果:
Cuboid 1: Cuboid(3, 2, 1)
Cuboid 2: Cuboid(4, 1, 5)
Result: Cuboid(7, 2, 5)
作业
作业 1:自定义立方体类,用友元函数访问它的私有成员
要求:自定义一个立方体类 Cuboid
,编写一个友元函数来输出该立方体对象的属性信息。
#include <iostream>
class Cuboid {
private:
double length, width, height;
public:
Cuboid(double l, double w, double h) : length(l), width(w), height(h) {}
// 在这里添加友元函数声明
friend void printCuboidInfo(const Cuboid& c);
};
// 在这里定义友元函数
void printCuboidInfo(const Cuboid& c)
{
std::cout << "Cuboid(" << c.length << ", " << c.width << ", " << c.height << ")" << std::endl;
}
int main() {
Cuboid c(3.0, 2.0, 4.0);
printCuboidInfo(c); // 使用友元函数输出立方体信息
return 0;
}
输出结果:
Cuboid(3, 2, 4)
作业 2: 自定义立方体类,用友元函数实现立方体相加
#include <iostream>
class Cuboid {
private:
double length, width, height;
public:
Cuboid(double l, double w, double h) : length(l), width(w), height(h) {}
friend Cuboid addCuboid(const Cuboid& c1, const Cuboid& c2);
friend void printCuboidInfo(const Cuboid& c);
};
Cuboid addCuboid(const Cuboid& c1, const Cuboid& c2)
{
return Cuboid(c1.length + c2.length, c1.width + c2.width, c1.height + c2.height);
}
void printCuboidInfo(const Cuboid& c)
{
std::cout << "Cuboid(" << c.length << ", " << c.width << ", " << c.height << ")" << std::endl;
}
int main() {
Cuboid c1(3.0, 2.0, 4.0);
Cuboid c2(1.0, 3.0, 5.0);
Cuboid c3 = addCuboid(c1, c2); // 使用友元函数输出立方体信息
printCuboidInfo(c3);
return 0;
}
总结与注意事项
- 友元函数和友元类都是通过
friend
关键字进行声明的。 - 友元可以访问类的私有和受保护成员,但该特性应谨慎使用,避免破坏类的封装性。
- 友元函数在重载运算符、多类协作等特殊需求中尤其有用。
- 友元关系是单向的,不具有传递性,且不能被继承。
- 过度使用友元会降低代码的可维护性和可读性,应该尽量使用类的公有接口来实现功能。 只有在确实需要访问私有成员,并且没有其他更好的解决方案时,才考虑使用友元。
- 将友元声明集中在类的开头或结尾, 提高代码可读性。