Skip to content

第 20 节 课件: 友元及友元相关内容

目录

  • 友元简介
  • 关键字 friend
    1. 友元函数
    • 定义友元函数
    • 友元函数的权限
    • 友元函数的特性与示例
    • 友元函数的优缺点
    1. 友元类
    • 定义友元类
    • 友元类的特性与示例
    • 友元关系的特性(单向性、非传递性)
    1. 友元的继承
    • 友元的访问权限继承
    • 友元继承示例
    • 友元与继承的关系
    1. 运算符重载友元
    • 友元作为运算符重载函数
    • 示例:友元运算符重载
  • 作业
    • 自定义立方体类,用友元函数访问它的私有成员
    • 自定义立方体类,用友元函数实现立方体相加
  • 总结与注意事项

友元简介

友元(friend)是 C++ 里特定的定义,用于扩展类的访问权限。普通情况下,类的私有属性和方法是不能被外部直接访问的。但通过声明友元函数或友元类,可以使这些函数或类可以访问当前类的私有属性和方法。

友元分为友元函数和友元类:

  1. 友元函数(Function Friend):独立函数或类中的函数可以作为另一个类的友元函数,从而访问该类的私有及保护成员。

  2. 友元类(Class Friend):一个完整的类可以作为另一个类的友元类,能够访问被友元声明的类中的所有私有、受保护成员。

关键字 friend

friend 关键字用来指定友元关系:

cpp
class MyClass {
    friend void myFriendFunction(MyClass &obj); // 友元函数声明
    friend class MyFriendClass;                // 友元类声明
};

友元函数和友元类的具体应用在接下来的部分详细讲解。

1. 友元函数

1.1 定义友元函数

可以通过 friend 关键字在类中声明一个函数为友元函数,从而使该函数能够访问该类的私有和受保护成员。友元函数可以是类的成员函数或者非成员函数。非成员友元函数既不是类的成员,也不是类对象的一部分。

定义友元函数的方法如下:

cpp
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 友元函数的特性与示例

友元函数并不是类的成员函数,虽然它们被编译器认为是类的朋友。以下是一个完整的代码示例解释友元函数的特性:

cpp
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 定义友元类

友元类是一个类,它可以访问另一个类的私有和受保护成员。友元类的声明方法如下:

cpp
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 友元类的特性与示例

友元类拥有被友元声明类的所有访问权限。举例如下:

cpp
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 友元类强调

友元关系的特性

  1. 单向性(Unidirectional): 如果 ClassAClassB 的友元类,ClassB不是 ClassA 的友元类。 友元关系不具有对称性。

  2. 非传递性(Non-transitive): 如果 ClassAClassB 的友元,ClassBClassC 的友元,这并不意味着 ClassAClassC 的友元。

3. 友元的继承

3.1 友元的访问权限继承

友元访问权限只限于子类中父类部分私有和受保护成员,子类新添加的成员无法被父类友元访问。

3.2 友元继承示例

cpp
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 友元作为运算符重载函数

运算符重载可以通过友元函数来完成,尤其是需要操作两个不同类对象的操作时。举例来说,重载 + 运算符使其能相加两个类对象:

cpp
#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 示例:友元运算符重载

让我们继续使用自定义立方体类的状况,以重载 <<+ 运算符。

cpp
#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,编写一个友元函数来输出该立方体对象的属性信息。

cpp
#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: 自定义立方体类,用友元函数实现立方体相加

c++
#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 关键字进行声明的。
  • 友元可以访问类的私有和受保护成员,但该特性应谨慎使用,避免破坏类的封装性。
  • 友元函数在重载运算符、多类协作等特殊需求中尤其有用。
  • 友元关系是单向的,不具有传递性,且不能被继承。
  • 过度使用友元会降低代码的可维护性和可读性,应该尽量使用类的公有接口来实现功能。 只有在确实需要访问私有成员,并且没有其他更好的解决方案时,才考虑使用友元。
  • 将友元声明集中在类的开头或结尾, 提高代码可读性。