C++友元机制的引入
我们知道面向对象具有封装和隐藏私有信息的特性。只有类的成员函数才能访问类的私有成员,而外部函数只能访问类的公有成员。通常来说,只会将必要的成员变量/函数定义为公有的。
在c++中有些场景是会在函数中访问其他类的私有成员,先知道有这种场景就行,后面会举例说明。为了解决这种问题,我们能想到的一个办法是把类中的该私有成员定义成公有的,但是这样做又会破会类的隐藏特性。所以c++提供了一种新的特性,友元机制。友元是一种定义在类外部的普通函数,但它需要在类体内进行说明,友元函数需要在类声明的地方加上friend关键字。 友元函数不是成员函数,但是它却可以访问类中的私有成员,就像给它开了一个特殊的访问权限一样。
友元函数的特点
友元是c++中的特有语法,在java中是取消了这种设计。
C++中的友元函数具有以下特点:
- 友元函数在类中通过
friend关键字进行声明; - 友元函数的声明是类中,但定义在类的外部;
- 友元函数可以访问类的所有成员,包括私有成员、保护成员和公共成员。
- 友元函数是个普通函数,不是类的成员函数,因此它没有隐式的this指针。
- 友元函数可以在类的任何地方声明,不受类访问限定符限制。
- 一个函数可以是多个类的友元函数
友元函数使用
友元函数参数的几种情况
因为友元函数没有this指针,则函数参数要有三种情况:
- 要访问非static成员时,需要对象做参数;
- 要访问static成员或全局变量时,则不需要对象做参数;
- 如果做参数的对象是全局对象,则不需要对象做参数;
友元函数的简单案例
class A {
private:
int x = 100;
public:
friend void friendFunc(const A &a); //申明友元函数
};
//在类的外部定义友元函数
void friendFunc(const A &a){
cout<< a.x<<endl;
}
int main() {
A a;
friendFunc(a);
return 0;
}
在这个demo中,在类A中申明了友元函数,然后在类的外部定义友元函数,这样在友元函数体中就可以访问到类A的私有变量了。
友元函数的实际案例
在c++操作符重载的时候,需要在操作符重载函数中访问类的私有成员,很多情况是只能使用友元函数。
如下案例,使用+重载运算符,就必须要用友元函数
class MyClass {
private:
int _num;
public:
MyClass(int data) : _num(data) {}
friend int operator+(const MyClass &obj1, const MyClass &obj2);
};
int operator+(const MyClass &obj1, const MyClass &obj2) {
int sum = obj1._num + obj2._num;
return sum;
}
int main() {
MyClass obj1(10);
MyClass obj2(10);
int result = obj1 + obj2;
cout << result << endl;
return 0;
}
<<输出运算符重载,由于第一个参数不是this指针,也是必须要使用友元函数
class MyClass {
public:
MyClass(string name, int age) : _name(name), _age(age) {}
friend ostream &operator<<(ostream &os, const MyClass &obj);
private:
string _name;
int _age;
};
ostream &operator<<(ostream &os, const MyClass &obj) {
os << "MyClass : " << obj._name << ", " << obj._age;
return os;
}
int main() {
MyClass obj("lisi", 20);
cout << obj << endl;
return 0;
}
友元类的特点
当一个类作为另一个类的友元时,这就意味着这个类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的非公有成员。
C++中的友元类具有以下特点:
- 友元类的声明使用
friend关键字在另一个类中进行,一般在类的声明中进行。 - 友元类可以访问被声明为友元的类的所有成员,包括私有成员、保护成员和公共成员。
- 友元类的成员函数可以直接访问被声明为友元的类的成员,无需通过对象名和作用域解析运算符。
- 友元关系是单向的,不具有交换性。
- 友元关系无法被继承。
友元类的使用
class B; // 前向声明B类
class A {
private:
int _data;
public:
A(int value) : _data(value) {}
friend class B; // 将B类声明为友元类
void printData() {
cout << _data << endl;
}
};
class B {
public:
void modifyData(A &a, int value) {
a._data = value; // 可以访问A类的私有成员变量data
}
};
int main() {
A a(5);
B b;
b.modifyData(a, 10);
a.printData(); // 输出: Data: 10
return 0;
}
在上面的demo中,类B是类A的友元类。所以B的所有成员函数中都可以访问类A的私有成员变量。
总结
可能需要使用友元类和友元函数的场景
- 访问私有成员变量:当一个类需要访问另一个类的私有成员变量时,可以使用友元类或友元函数。这种情况可能发生在两个紧密相关的类之间,其中一个类需要直接访问另一个类的私有数据。
- 优化性能:有时,为了提高性能,可能需要将某个函数声明为另一个类的友元函数,以便直接访问其私有成员变量,而不需要通过公共接口来访问。
尽管友元关系可以解决某些问题,但它们也存在一些缺点
- 破坏封装性:友元关系允许外部类或函数直接访问另一个类的私有成员变量,这可能破坏类的封装性原则。这会增加代码的耦合性,并使代码更难以维护和理解。
- 降低可扩展性:使用友元关系可能会导致代码的可扩展性降低。如果一个类的私有成员变量需要在多个类之间共享,那么随着代码的发展,可能需要修改多个类的定义,这会增加代码的复杂性和维护成本。
- 安全性问题:友元关系可能导致安全性问题,因为它允许外部类或函数绕过公共接口直接访问和修改私有成员变量。这可能导致数据的不一致性和潜在的错误。
因此,在设计和实现C++类时,应该谨慎使用友元关系,并优先考虑使用公共接口来访问和修改私有数据。只有在确实需要在类之间共享私有成员变量,并且没有更好的替代方法时,才应该考虑使用友元类和友元函数。