• 类的主要特点之一是数据隐藏,也就是类的私有成员只能通过它的成员函数来访问
  • 有没有办法允许在类外对私有成员进行操作呢?
  • 友元可以是一般函数,也可以是另外一个类的成员函数,当函数被声明为一个类的友元函数后,它就可以通过对象名访问类的私有成员和保护成员。 还可以是整个的一个类(类中所有成员函数都成为友元函数)。

    一、普通函数做友元

    普通函数作为类的友元函数后,可以通过对象访问类内成员数据。
    image.png
    注意:

  • 友元函数不是类的成员函数,类外定义时,不能在函数名前加“类名::”,也不能通过对象来引用友元函数

  • 友元函数在公有部分或私有部分均可声明
  • 友元函数在使用时不能直接调用成员函数,也不能通过this指针引用对象成员,它必须通过入口参数传递的对象名或指针来引用对象的成员(不是该类的成员函数)。因此友元函数一般带有一个该类的入口参数。
  • 当一个函数需要访问多个类时,应该把这个函数同时定义为这些类的友元函数,这样,这个函数才能访问这些类的数据。
  • 如果友元函数带了两个不同的类的对象,其中一个对象所对应的类要在后面声明。为了避免编译时的错误,编程时必须通过向前引用告诉C++,该类将在后面定义。
  • 声明友元函数会破坏类的隐蔽性和封装性。 ```cpp 1、当一个函数需要访问多个类时,应该把这个函数同时定义为这些类的友元函数 2、如果友元函数带了两个不同的类的对象,其中一个对象所对应的类要在后面声明。

    include

    using namespace std; class Date; 2、 class Time { public: Time(int, int, int);//声明构造函数 friend void display(Time &);//声明一个友元函数 private: int hour; int minute; int second; }; class Date { public: Date(int, int, int);//声明构造函数 friend void display(Date &); private: int year; int month; int day; }; Time::Time(int h, int m, int s) { hour = h; minute = m; second = s; } void display(Date &d) { cout << d.month << “/“ << d.day << “/“ << d.year << endl;

} void display(Time &t) { cout << t.hour << “:” << t.minute << “:” << t.second << endl; } Date::Date(int y, int m, int d) { year = y; month = m; day = d; } int main() { Time t1(10, 13, 56); Date d1(12, 25, 2004); display(t1); display(d1); return 0; } 运行结果: 10:13:56 25/2004/12

  1. ```cpp
  2. 关于此示例,有两点值得注意。首先,由于 printWeather 是这两个类的朋友,因此它可以从两个类的对象访问私有数据。其次,请注意示例顶部的以下行:class Humidity;
  3. #include <iostream>
  4. class Humidity;
  5. class Temperature
  6. {
  7. private:
  8. int m_temp {};
  9. public:
  10. Temperature(int temp=0): m_temp { temp }{}
  11. friend void printWeather(const Temperature& temperature, const Humidity& humidity);
  12. };
  13. class Humidity
  14. {
  15. private:
  16. int m_humidity {};
  17. public:
  18. Humidity(int humidity=0): m_humidity {humidity}{}
  19. friend void printWeather(const Temperature& temperature, const Humidity& humidity);
  20. };
  21. void printWeather(const Temperature& temperature, const Humidity& humidity)
  22. {
  23. std::cout << "The temperature is " << temperature.m_temp <<
  24. " and the humidity is " << humidity.m_humidity << '\n';
  25. }
  26. int main()
  27. {
  28. Humidity hum{10};
  29. Temperature temp{12};
  30. printWeather(temp, hum);
  31. return 0;
  32. }

二、类的成员函数作为友元函数

一个类的成员函数可以作为另一个类的友元,这种成员函数不仅可以访问自己所在类中的成员,还可以通过对象名访问friend声明语句所在类的私有成员和保护成员,从而使两个类相互合作。
image.png

  1. 错误代码
  2. #include <iostream>
  3. class Display; // forward declaration for class Display
  4. class Storage
  5. {
  6. private:
  7. int m_nValue {};
  8. double m_dValue {};
  9. public:
  10. Storage(int nValue, double dValue): m_nValue { nValue }, m_dValue { dValue }{}
  11. // Make the Display::displayItem member function a friend of the Storage class
  12. friend void Display::displayItem(const Storage& storage); // error: Storage hasn't seen the full definition of class Display
  13. };
  14. class Display
  15. {
  16. private:
  17. bool m_displayIntFirst {};
  18. public:
  19. Display(bool displayIntFirst): m_displayIntFirst { displayIntFirst }{}
  20. void displayItem(const Storage& storage)
  21. {
  22. if (m_displayIntFirst)
  23. std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
  24. else // display double first
  25. std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
  26. }
  27. };
  28. 事实证明这是行不通的。为了使成员函数成为朋友,编译器必须看到朋友成员函数类的完整定义(而不仅仅是前向声明)。由于类 Storage 尚未看到类 Display 的完整定义,因此编译器将在我们尝试使成员函数成为朋友的位置出错。
  29. 幸运的是,只需将类 Display 的定义移到类 Storage 的定义之前,即可轻松解决此问题。
  30. 修改:
  31. #include <iostream>
  32. class Display
  33. {
  34. private:
  35. bool m_displayIntFirst {};
  36. public:
  37. Display(bool displayIntFirst):m_displayIntFirst { displayIntFirst }{}
  38. void displayItem(const Storage& storage) // error: compiler doesn't know what a Storage is
  39. {
  40. if (m_displayIntFirst)
  41. std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
  42. else // display double first
  43. std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
  44. }
  45. };
  46. class Storage
  47. {
  48. private:
  49. int m_nValue {};
  50. double m_dValue {};
  51. public:
  52. Storage(int nValue, double dValue): m_nValue { nValue }, m_dValue { dValue }{}
  53. // Make the Display::displayItem member function a friend of the Storage class
  54. friend void Display::displayItem(const Storage& storage); // okay now
  55. };
  56. 但是,我们现在有另一个问题。由于成员函数 Display::d isplayItem() 使用 Storage 作为参考参数,而我们只是将 Storage 的定义移到 Display 的定义下面,编译器会抱怨它不知道 Storage 是什么。我们无法通过重新排列定义顺序来解决此问题,因为这样我们将撤消之前的修复。
  57. 幸运的是,这也可以通过几个简单的步骤来解决。首先,我们可以添加类 Storage 作为正向声明。其次,我们可以将 Display::d isplayItem() 的定义移出类,在存储类的完整定义之后。
  58. 最终:
  59. #include <iostream>
  60. class Storage; // forward declaration for class Storage
  61. class Display
  62. {
  63. private:
  64. bool m_displayIntFirst {};
  65. public:
  66. Display(bool displayIntFirst): m_displayIntFirst { displayIntFirst }{}
  67. void displayItem(const Storage& storage); // forward declaration above needed for this declaration line
  68. };
  69. class Storage // full definition of Storage class
  70. {
  71. private:
  72. int m_nValue {};
  73. double m_dValue {};
  74. public:
  75. Storage(int nValue, double dValue):m_nValue { nValue }, m_dValue { dValue }{}
  76. // Make the Display::displayItem member function a friend of the Storage class (requires seeing the full declaration of class Display, as above)
  77. friend void Display::displayItem(const Storage& storage);
  78. };
  79. // Now we can define Display::displayItem, which needs to have seen the full definition of class Storage
  80. void Display::displayItem(const Storage& storage)
  81. {
  82. if (m_displayIntFirst)
  83. std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
  84. else // display double first
  85. std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
  86. }
  87. int main()
  88. {
  89. Storage storage(5, 6.7);
  90. Display display(false);
  91. display.displayItem(storage);
  92. return 0;
  93. }
  94. 现在一切都可以正确编译:类 Storage 的前向声明足以满足 Display::d isplayItem() 的声明,
  95. Display 的完整定义满足将 Display::d isplayItem() 声明为 Storage 的朋友,
  96. Storage 的完整定义足以满足成员函数 Display::d isplayItem() 的定义。
  97. 如果这有点令人困惑,请参阅上面程序中的评论。
  98. 如果这看起来像是一种痛苦 - 那就是。幸运的是,这种舞蹈是必要的,因为我们试图在一个文件中完成所有事情。
  99. 更好的解决方案是将每个类定义放在单独的头文件中,并将成员函数定义放在相应的.cpp文件中。
  100. 这样,所有类定义都可以在.cpp文件中立即看到,并且不需要重新排列类或函数!
  101. 总结
  102. 友元函数或类是可以访问另一个类的私有成员的函数或类,就好像它是该类的成员一样。
  103. 这允许朋友函数或朋友类与另一个类紧密合作,而不会使另一个类公开其私有成员(例如,通过访问函数)。

三、友元类

  • 友元函数可以使函数能够访问某个类中的私有或保护成员。如果类A的所有成员函数都想访问类B的私有或保护成员,一种方法是将类A的所有成员函数都声明为类B的友元函数,但这样做显得比较麻烦。这时可以采用友元类
  • 友元关系是单行的,不能传递。
  • 友元可以声明在公有或私有,语义相同。

image.pngimage.png

#include <iostream>
using namespace std;

class Complex
{
private:
    double real;
    double imag;
public:
    Complex(double r, double i):real(r),imag(i){}
    friend class Vector;                            将Vector声明为Complex的友元类
};

class Vector
{
private:
    double x, y;
public:
    void Change(Complex c);
    void Print(Complex c);
};

void Vector::Change(Complex c)
{
    x = c.real;
    y = c.imag;
}

void Vector::Print(Complex c)
{
    cout << "复数:";
    cout << c.real;
    if (c.imag > 0)
        cout << "+";
    cout << c.imag << "i" << endl;
    cout << "Vector:";
    cout << "(" << x << "," << y << ")" << endl;
}

int main()
{
    Complex c(1.1, 2.2);
    Vector v;
    v.Change(c);
    v.Print(c);
    return 0;
}