- 类的主要特点之一是数据隐藏,也就是类的私有成员只能通过它的成员函数来访问。
- 有没有办法允许在类外对私有成员进行操作呢?
友元可以是一般函数,也可以是另外一个类的成员函数,当函数被声明为一个类的友元函数后,它就可以通过对象名访问类的私有成员和保护成员。 还可以是整个的一个类(类中所有成员函数都成为友元函数)。
一、普通函数做友元
普通函数作为类的友元函数后,可以通过对象访问类内成员数据。
注意:友元函数不是类的成员函数,类外定义时,不能在函数名前加“类名::”,也不能通过对象来引用友元函数
- 友元函数在公有部分或私有部分均可声明
- 友元函数在使用时不能直接调用成员函数,也不能通过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
```cpp
关于此示例,有两点值得注意。首先,由于 printWeather 是这两个类的朋友,因此它可以从两个类的对象访问私有数据。其次,请注意示例顶部的以下行:class Humidity;
#include <iostream>
class Humidity;
class Temperature
{
private:
int m_temp {};
public:
Temperature(int temp=0): m_temp { temp }{}
friend void printWeather(const Temperature& temperature, const Humidity& humidity);
};
class Humidity
{
private:
int m_humidity {};
public:
Humidity(int humidity=0): m_humidity {humidity}{}
friend void printWeather(const Temperature& temperature, const Humidity& humidity);
};
void printWeather(const Temperature& temperature, const Humidity& humidity)
{
std::cout << "The temperature is " << temperature.m_temp <<
" and the humidity is " << humidity.m_humidity << '\n';
}
int main()
{
Humidity hum{10};
Temperature temp{12};
printWeather(temp, hum);
return 0;
}
二、类的成员函数作为友元函数
一个类的成员函数可以作为另一个类的友元,这种成员函数不仅可以访问自己所在类中的成员,还可以通过对象名访问friend声明语句所在类的私有成员和保护成员,从而使两个类相互合作。
错误代码
#include <iostream>
class Display; // forward declaration for class Display
class Storage
{
private:
int m_nValue {};
double m_dValue {};
public:
Storage(int nValue, double dValue): m_nValue { nValue }, m_dValue { dValue }{}
// Make the Display::displayItem member function a friend of the Storage class
friend void Display::displayItem(const Storage& storage); // error: Storage hasn't seen the full definition of class Display
};
class Display
{
private:
bool m_displayIntFirst {};
public:
Display(bool displayIntFirst): m_displayIntFirst { displayIntFirst }{}
void displayItem(const Storage& storage)
{
if (m_displayIntFirst)
std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
else // display double first
std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
}
};
事实证明这是行不通的。为了使成员函数成为朋友,编译器必须看到朋友成员函数类的完整定义(而不仅仅是前向声明)。由于类 Storage 尚未看到类 Display 的完整定义,因此编译器将在我们尝试使成员函数成为朋友的位置出错。
幸运的是,只需将类 Display 的定义移到类 Storage 的定义之前,即可轻松解决此问题。
修改:
#include <iostream>
class Display
{
private:
bool m_displayIntFirst {};
public:
Display(bool displayIntFirst):m_displayIntFirst { displayIntFirst }{}
void displayItem(const Storage& storage) // error: compiler doesn't know what a Storage is
{
if (m_displayIntFirst)
std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
else // display double first
std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
}
};
class Storage
{
private:
int m_nValue {};
double m_dValue {};
public:
Storage(int nValue, double dValue): m_nValue { nValue }, m_dValue { dValue }{}
// Make the Display::displayItem member function a friend of the Storage class
friend void Display::displayItem(const Storage& storage); // okay now
};
但是,我们现在有另一个问题。由于成员函数 Display::d isplayItem() 使用 Storage 作为参考参数,而我们只是将 Storage 的定义移到 Display 的定义下面,编译器会抱怨它不知道 Storage 是什么。我们无法通过重新排列定义顺序来解决此问题,因为这样我们将撤消之前的修复。
幸运的是,这也可以通过几个简单的步骤来解决。首先,我们可以添加类 Storage 作为正向声明。其次,我们可以将 Display::d isplayItem() 的定义移出类,在存储类的完整定义之后。
最终:
#include <iostream>
class Storage; // forward declaration for class Storage
class Display
{
private:
bool m_displayIntFirst {};
public:
Display(bool displayIntFirst): m_displayIntFirst { displayIntFirst }{}
void displayItem(const Storage& storage); // forward declaration above needed for this declaration line
};
class Storage // full definition of Storage class
{
private:
int m_nValue {};
double m_dValue {};
public:
Storage(int nValue, double dValue):m_nValue { nValue }, m_dValue { dValue }{}
// Make the Display::displayItem member function a friend of the Storage class (requires seeing the full declaration of class Display, as above)
friend void Display::displayItem(const Storage& storage);
};
// Now we can define Display::displayItem, which needs to have seen the full definition of class Storage
void Display::displayItem(const Storage& storage)
{
if (m_displayIntFirst)
std::cout << storage.m_nValue << ' ' << storage.m_dValue << '\n';
else // display double first
std::cout << storage.m_dValue << ' ' << storage.m_nValue << '\n';
}
int main()
{
Storage storage(5, 6.7);
Display display(false);
display.displayItem(storage);
return 0;
}
现在一切都可以正确编译:类 Storage 的前向声明足以满足 Display::d isplayItem() 的声明,
Display 的完整定义满足将 Display::d isplayItem() 声明为 Storage 的朋友,
类 Storage 的完整定义足以满足成员函数 Display::d isplayItem() 的定义。
如果这有点令人困惑,请参阅上面程序中的评论。
如果这看起来像是一种痛苦 - 那就是。幸运的是,这种舞蹈是必要的,因为我们试图在一个文件中完成所有事情。
更好的解决方案是将每个类定义放在单独的头文件中,并将成员函数定义放在相应的.cpp文件中。
这样,所有类定义都可以在.cpp文件中立即看到,并且不需要重新排列类或函数!
总结
友元函数或类是可以访问另一个类的私有成员的函数或类,就好像它是该类的成员一样。
这允许朋友函数或朋友类与另一个类紧密合作,而不会使另一个类公开其私有成员(例如,通过访问函数)。
三、友元类
- 友元函数可以使函数能够访问某个类中的私有或保护成员。如果类A的所有成员函数都想访问类B的私有或保护成员,一种方法是将类A的所有成员函数都声明为类B的友元函数,但这样做显得比较麻烦。这时可以采用友元类。
- 友元关系是单行的,不能传递。
- 友元可以声明在公有或私有,语义相同。
#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;
}